Repository: y-scope/clp
Pull request #873: feat(kv-ir): Implement `QueryHandler`'s factory function and `update_partially_resolved_columns`.
Author: login: LinZhihao-723
Created: 2025-04-30T23:59:06Z | Merged: 2025-05-05T21:33:55Z

<!-- markdownlint-disable MD012 -->

<!--
Set the PR title to a meaningful commit message that:

* is in imperative form.
* follows the Conventional Commits specification (https://www.conventionalcommits.org).
  * See https://github.com/commitizen/conventional-commit-types/blob/master/index.json for possible
    types.

Example:

fix: Don't add implicit wildcards ('*') at the beginning and the end of a query (fixes #390).
-->

# Description

<!-- Describe what this request will change/fix and provide any details necessary for reviewers. -->

## References

- This is one of the PR series #840.
- This PR depends on #879 to resolve all clang-tidy warnings.

## Overview

This PR adds partial implementation for `QueryHandler`. To avoid making the entire handler as a template class, the actual implementation is in `QueryHandlerImpl`. The current PR adds implementation for:
- Factory function
- `update_partially_resolved_columns`

# Checklist

<!-- Ensure each item below is satisfied and indicate so by inserting an `x` within each `[ ]`. -->

* [x] The PR satisfies the [contribution guidelines][yscope-contrib-guidelines].
* [x] This is a breaking change and that has been indicated in the PR title, OR this isn't a
  breaking change.
* [x] Necessary docs have been updated, OR no docs need to be updated.

# Validation performed

<!-- Describe what tests and validation you performed on the change. -->

* [x] Ensure all workflows passed.
* [x] Add unit tests to cover:
  - Column resolution on search AST.
  - Column resolution on projections.

[yscope-contrib-guidelines]: https://docs.yscope.com/dev-guide/contrib-guides-overview.html


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

## Summary by CodeRabbit

- **New Features**
  - Introduced advanced query handling and schema tree column resolution for IR stream search, including support for new error codes and detailed error messages.
  - Added new query handler implementation enabling preprocessing, projection, and evaluation of search queries with case sensitivity options.
  - Implemented token iterator and partial resolution mechanisms for schema tree nodes with wildcard support.
  - Provided a factory method to create fully initialized query handlers with projection mapping.
  - Added utilities and classes to generate and manage schema tree column queries and projections.

- **Bug Fixes**
  - Improved error reporting by expanding error code coverage for various query and projection failure scenarios.

- **Tests**
  - Added comprehensive test suites to verify column resolution and projection handling against complex schema trees.
  - Introduced utilities and helpers to support robust testing of schema tree queries and projections.

- **Documentation**
  - Enhanced documentation for new classes, methods, and utility functions related to schema tree search and testing.

- **Refactor**
  - Refactored query handler interface to provide full method implementations and improved internal delegation.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Linked issue #390: clp-s: Implicit wildcard behaviour is unintuitive
### Request

For clp-s the way that we treat wildcards is unintuitive.

The rules are that if a user adds any explicit wildcards we don't add any implicit ones, and we only add implicit wildcards at the start and end if the predicate contains a space.

To make things simpler for users we should make the user use explicit wildcards for everything.

### Possible implementation

Instead of conditionally adding wildcards when processing a query we will simply not add implicit wildcards.

Diff:
diff --git a/components/core/CMakeLists.txt b/components/core/CMakeLists.txt
index b0c79a9768..c0eb7c5f12 100644
--- a/components/core/CMakeLists.txt
+++ b/components/core/CMakeLists.txt
@@ -457,7 +457,12 @@ set(SOURCE_FILES_unitTest
         src/clp/ffi/ir_stream/search/ErrorCode.hpp
         src/clp/ffi/ir_stream/search/NewProjectedSchemaTreeNodeCallbackReq.hpp
         src/clp/ffi/ir_stream/search/QueryHandler.hpp
+        src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
+        src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
+        src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp
         src/clp/ffi/ir_stream/search/test/test_utils.cpp
+        src/clp/ffi/ir_stream/search/test/utils.cpp
+        src/clp/ffi/ir_stream/search/test/utils.hpp
         src/clp/ffi/ir_stream/search/utils.cpp
         src/clp/ffi/ir_stream/search/utils.hpp
         src/clp/ffi/ir_stream/utils.cpp
diff --git a/components/core/src/clp/ffi/ir_stream/search/ErrorCode.cpp b/components/core/src/clp/ffi/ir_stream/search/ErrorCode.cpp
index f095ec41c4..79868bfac6 100644
--- a/components/core/src/clp/ffi/ir_stream/search/ErrorCode.cpp
+++ b/components/core/src/clp/ffi/ir_stream/search/ErrorCode.cpp
@@ -17,6 +17,14 @@ auto ErrorCategory::name() const noexcept -> char const* {
 template <>
 auto ErrorCategory::message(ErrorCodeEnum error_enum) const -> std::string {
     switch (error_enum) {
+        case ErrorCodeEnum::AstDynamicCastFailure:
+            return "Failed to dynamically cast an AST node to the expected type.";
+        case ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds:
+            return "Attempted to access a token beyond the end of the column descriptor.";
+        case ErrorCodeEnum::ColumnTokenizationFailure:
+            return "Failed to tokenize the column descriptor.";
+        case ErrorCodeEnum::DuplicateProjectedColumn:
+            return "The projected column is not unique.";
         case ErrorCodeEnum::EncodedTextAstDecodingFailure:
             return "Failed to decode the given encoded text AST.";
         case ErrorCodeEnum::LiteralTypeUnexpected:
@@ -25,6 +33,12 @@ auto ErrorCategory::message(ErrorCodeEnum error_enum) const -> std::string {
             return "Unsupported literal type.";
         case ErrorCodeEnum::MethodNotImplemented:
             return "The requested method is not implemented.";
+        case ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure:
+            return "Failed to create a column descriptor for the given projection.";
+        case ErrorCodeEnum::QueryExpressionIsNull:
+            return "The query expression is NULL.";
+        case ErrorCodeEnum::QueryTransformationPassFailed:
+            return "Failed to execute transformation passes on the query expression.";
         default:
             return "Unknown error code enum.";
     }
diff --git a/components/core/src/clp/ffi/ir_stream/search/ErrorCode.hpp b/components/core/src/clp/ffi/ir_stream/search/ErrorCode.hpp
index 873432acee..9198ffbca9 100644
--- a/components/core/src/clp/ffi/ir_stream/search/ErrorCode.hpp
+++ b/components/core/src/clp/ffi/ir_stream/search/ErrorCode.hpp
@@ -10,10 +10,17 @@ namespace clp::ffi::ir_stream::search {
  * Represents all possible error codes related to KV-pair IR stream search.
  */
 enum class ErrorCodeEnum : uint8_t {
+    AstDynamicCastFailure = 1,
+    ColumnDescriptorTokenIteratorOutOfBounds,
+    ColumnTokenizationFailure,
+    DuplicateProjectedColumn,
     EncodedTextAstDecodingFailure,
     LiteralTypeUnexpected,
     LiteralTypeUnsupported,
     MethodNotImplemented,
+    ProjectionColumnDescriptorCreationFailure,
+    QueryTransformationPassFailed,
+    QueryExpressionIsNull,
 };
 
 using ErrorCode = ystdlib::error_handling::ErrorCode<ErrorCodeEnum>;
diff --git a/components/core/src/clp/ffi/ir_stream/search/QueryHandler.hpp b/components/core/src/clp/ffi/ir_stream/search/QueryHandler.hpp
index 78097b6fe6..f995a18062 100644
--- a/components/core/src/clp/ffi/ir_stream/search/QueryHandler.hpp
+++ b/components/core/src/clp/ffi/ir_stream/search/QueryHandler.hpp
@@ -1,13 +1,20 @@
 #ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLER_HPP
 #define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLER_HPP
 
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
 #include <outcome/outcome.hpp>
 
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
 #include "../../KeyValuePairLogEvent.hpp"
 #include "../../SchemaTree.hpp"
 #include "AstEvaluationResult.hpp"
-#include "ErrorCode.hpp"
 #include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "QueryHandlerImpl.hpp"
 
 namespace clp::ffi::ir_stream::search {
 /**
@@ -28,11 +35,26 @@ class QueryHandler {
      * @param new_projected_schema_tree_node_callback
      * @return A result containing the newly constructed `QueryHandler` on success, or an error code
      * indicating the failure:
-     * - TODO
+     * - Forwards `QueryHandlerImpl::create`'s return values.
      */
     [[nodiscard]] static auto create(
-            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
-    ) -> outcome_v2::std_result<QueryHandler>;
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback,
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            std::vector<std::pair<std::string, clp_s::search::ast::LiteralTypeBitmask>> const&
+                    projections,
+            bool case_sensitive_match
+    ) -> outcome_v2::std_result<QueryHandler> {
+        return QueryHandler{
+                new_projected_schema_tree_node_callback,
+                OUTCOME_TRYX(
+                        QueryHandlerImpl::create(
+                                std::move(query),
+                                projections,
+                                case_sensitive_match
+                        )
+                )
+        };
+    }
 
     // Delete copy constructor and assignment operator
     QueryHandler(QueryHandler const&) = delete;
@@ -52,13 +74,20 @@ class QueryHandler {
      * @param node_locator
      * @param node_id
      * @return A void result on success, or an error code indicating the failure:
-     * - TODO
+     * - Forwards `QueryHandlerImpl::update_partially_resolved_columns`'s return values.
      */
     [[nodiscard]] auto update_partially_resolved_columns(
             bool is_auto_generated,
             SchemaTree::NodeLocator const& node_locator,
             SchemaTree::Node::id_t node_id
-    ) -> outcome_v2::std_result<void>;
+    ) -> outcome_v2::std_result<void> {
+        return m_query_handler_impl.update_partially_resolved_columns(
+                is_auto_generated,
+                node_locator,
+                node_id,
+                m_new_projected_schema_tree_node_callback
+        );
+    }
 
     /**
      * Evaluates the given node-ID-value pairs against the underlying query.
@@ -66,47 +95,30 @@ class QueryHandler {
      * @param user_gen_node_id_value_pairs
      * @return A result containing the evaluation result on success, or an error code indicating
      * the failure:
-     * - TODO
+     * - Forwards `QueryHandlerImpl::evaluate_node_id_value_pairs`'s return values.
      */
     [[nodiscard]] auto evaluate_node_id_value_pairs(
             KeyValuePairLogEvent::NodeIdValuePairs const& auto_gen_node_id_value_pairs,
             KeyValuePairLogEvent::NodeIdValuePairs const& user_gen_node_id_value_pairs
-    ) -> outcome_v2::std_result<AstEvaluationResult>;
+    ) -> outcome_v2::std_result<AstEvaluationResult> {
+        return m_query_handler_impl.evaluate_node_id_value_pairs(
+                auto_gen_node_id_value_pairs,
+                user_gen_node_id_value_pairs
+        );
+    }
 
 private:
     // Constructor
     explicit QueryHandler(
-            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback,
+            QueryHandlerImpl query_handler_impl
     )
-            : m_new_projected_schema_tree_node_callback{new_projected_schema_tree_node_callback} {}
+            : m_new_projected_schema_tree_node_callback{new_projected_schema_tree_node_callback},
+              m_query_handler_impl{std::move(query_handler_impl)} {}
 
     NewProjectedSchemaTreeNodeCallbackType m_new_projected_schema_tree_node_callback;
+    QueryHandlerImpl m_query_handler_impl;
 };
-
-template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
-auto QueryHandler<NewProjectedSchemaTreeNodeCallbackType>::create(
-        [[maybe_unused]] NewProjectedSchemaTreeNodeCallbackType
-                new_projected_schema_tree_node_callback
-) -> outcome_v2::std_result<QueryHandler<NewProjectedSchemaTreeNodeCallbackType>> {
-    return ErrorCode{ErrorCodeEnum::MethodNotImplemented};
-}
-
-template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
-auto QueryHandler<NewProjectedSchemaTreeNodeCallbackType>::update_partially_resolved_columns(
-        [[maybe_unused]] bool is_auto_generated,
-        [[maybe_unused]] SchemaTree::NodeLocator const& node_locator,
-        [[maybe_unused]] SchemaTree::Node::id_t node_id
-) -> outcome_v2::std_result<void> {
-    return ErrorCode{ErrorCodeEnum::MethodNotImplemented};
-}
-
-template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
-auto QueryHandler<NewProjectedSchemaTreeNodeCallbackType>::evaluate_node_id_value_pairs(
-        [[maybe_unused]] KeyValuePairLogEvent::NodeIdValuePairs const& auto_gen_node_id_value_pairs,
-        [[maybe_unused]] KeyValuePairLogEvent::NodeIdValuePairs const& user_gen_node_id_value_pairs
-) -> outcome_v2::std_result<AstEvaluationResult> {
-    return ErrorCode{ErrorCodeEnum::MethodNotImplemented};
-}
 }  // namespace clp::ffi::ir_stream::search
 
 #endif  // CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLER_HPP
diff --git a/components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp b/components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
new file mode 100644
index 0000000000..6fe14781fa
--- /dev/null
+++ b/components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
@@ -0,0 +1,329 @@
+#include "QueryHandlerImpl.hpp"
+
+#include <exception>
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/archive_constants.hpp"
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/ConvertToExists.hpp"
+#include "../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../clp_s/search/ast/NarrowTypes.hpp"
+#include "../../../../clp_s/search/ast/OrOfAndForm.hpp"
+#include "../../../../clp_s/search/ast/SearchUtils.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::EmptyExpr;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Pre-processes a search query by applying several transformation passes.
+ * @param query
+ * @return A result containing the transformed
+ * - ErrorCodeEnum::QueryExpressionIsNull if `query` is nullptr.
+ * - ErrorCodeEnum::QueryTransformationPassFailed if any of the transformation pass failed.
+ */
+[[nodiscard]] auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>>;
+
+/**
+ * Creates projected columns and column-to-original-key map from the given projections.
+ * @param projections
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - A vector of projected columns.
+ *   - A projected-column-to-original-key map.
+ * - The possible error codes:
+ *   - ErrorCodeEnum::DuplicateProjectedColumn if `projections` contains duplicated keys.
+ *   - ErrorCodeEnum::ColumnTokenizationFailure if failed to tokenize a projection key.
+ *   - ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure if failed to create a column
+ *     descriptor for the projected key.
+ */
+[[nodiscard]] auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>>;
+
+/**
+ * Creates initial partial resolutions for the given query and projections.
+ * @param query
+ * @param projected_column_to_original_key
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - The partial resolution for auto-generated keys.
+ *   - The partial resolution for user-generated keys.
+ * - The possible error codes:
+ *   - Forwards `initialize_partial_resolution_from_search_ast`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::create`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::next`'s return values.
+ */
+[[nodiscard]] auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>>;
+
+/**
+ * Initializes partial resolutions from a search AST.
+ * @param root The root of the search AST.
+ * @param auto_gen_namespace_partial_resolutions Returns initialized auto-gen partial resolutions.
+ * @param user_gen_namespace_partial_resolutions Returns initialized user-gen partial resolutions.
+ * @return A void result on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::AstDynamicCastFailure if failed to dynamically cast an AST node to a target
+ *   type.
+ */
+[[nodiscard]] auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void>;
+
+/**
+ * @param key_namespace
+ * @return Whether `key_namespace` is auto-generated or user-generated, or std::nullopt if the
+ * namespace is unrecognized.
+ */
+[[nodiscard]] auto is_auto_generated(std::string_view key_namespace) -> std::optional<bool>;
+
+auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>> {
+    if (nullptr == query) {
+        return ErrorCode{ErrorCodeEnum::QueryExpressionIsNull};
+    }
+
+    if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(query)) {
+        return query;
+    }
+
+    try {
+        if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        if (query = clp_s::search::ast::NarrowTypes{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        return clp_s::search::ast::ConvertToExists{}.run(query);
+    } catch (std::exception const& ex) {
+        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+    }
+}
+
+auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>> {
+    std::unordered_set<std::string_view> unique_projected_columns;
+    std::vector<std::shared_ptr<ColumnDescriptor>> projected_columns;
+    QueryHandlerImpl::ProjectionMap projected_column_to_original_key;
+
+    for (auto const& [key, types] : projections) {
+        if (unique_projected_columns.contains(key)) {
+            return ErrorCode{ErrorCodeEnum::DuplicateProjectedColumn};
+        }
+        unique_projected_columns.emplace(key);
+
+        std::vector<std::string> descriptor_tokens;
+        std::string descriptor_namespace;
+        if (false
+            == clp_s::search::ast::tokenize_column_descriptor(
+                    key,
+                    descriptor_tokens,
+                    descriptor_namespace
+            ))
+        {
+            return ErrorCode{ErrorCodeEnum::ColumnTokenizationFailure};
+        }
+
+        try {
+            auto column_descriptor{clp_s::search::ast::ColumnDescriptor::create_from_escaped_tokens(
+                    descriptor_tokens,
+                    descriptor_namespace
+            )};
+            column_descriptor->set_matching_types(types);
+            if (column_descriptor->is_unresolved_descriptor()
+                || column_descriptor->get_descriptor_list().empty())
+            {
+                return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+            }
+            projected_column_to_original_key.emplace(column_descriptor.get(), key);
+            projected_columns.emplace_back(std::move(column_descriptor));
+        } catch (std::exception const& e) {
+            return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+        }
+    }
+
+    return {std::move(projected_columns), std::move(projected_column_to_original_key)};
+}
+
+auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>> {
+    QueryHandlerImpl::PartialResolutionMap auto_gen_namespace_partial_resolutions;
+    QueryHandlerImpl::PartialResolutionMap user_gen_namespace_partial_resolutions;
+    for (auto const& [col, key] : projected_column_to_original_key) {
+        auto const optional_is_auto_gen{is_auto_generated(col->get_namespace())};
+        if (false == optional_is_auto_gen.has_value()) {
+            // Ignore unrecognized namespace
+            continue;
+        }
+        auto& partial_resolutions{
+                optional_is_auto_gen.value() ? auto_gen_namespace_partial_resolutions
+                                             : user_gen_namespace_partial_resolutions
+        };
+        auto [it, inserted] = partial_resolutions.try_emplace(
+                SchemaTree::cRootId,
+                std::vector<QueryHandlerImpl::ColumnDescriptorTokenIterator>{}
+        );
+        it->second.emplace_back(
+                OUTCOME_TRYX(QueryHandlerImpl::ColumnDescriptorTokenIterator::create(col))
+        );
+    }
+
+    OUTCOME_TRYV(initialize_partial_resolution_from_search_ast(
+            query,
+            auto_gen_namespace_partial_resolutions,
+            user_gen_namespace_partial_resolutions
+    ));
+
+    return {std::move(auto_gen_namespace_partial_resolutions),
+            std::move(user_gen_namespace_partial_resolutions)};
+}
+
+auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void> {
+    std::vector<Expression*> ast_dfs_stack;
+    ast_dfs_stack.emplace_back(root.get());
+    while (false == ast_dfs_stack.empty()) {
+        auto* expr{ast_dfs_stack.back()};
+        ast_dfs_stack.pop_back();
+        if (expr->has_only_expression_operands()) {
+            for (auto it{expr->op_begin()}; it != expr->op_end(); ++it) {
+                auto* child_expr{dynamic_cast<Expression*>(it->get())};
+                if (nullptr == child_expr) {
+                    return ErrorCode{ErrorCodeEnum::AstDynamicCastFailure};
+                }
+                ast_dfs_stack.emplace_back(child_expr);
+            }
+            continue;
+        }
+
+        auto* filter{dynamic_cast<FilterExpr*>(expr)};
+        if (nullptr == filter) {
+            continue;
+        }
+
+        auto* col{filter->get_column().get()};
+        if (col->is_pure_wildcard() || col->get_descriptor_list().empty()) {
+            // If the column is a single wildcard, we will resolve it dynamically during the AST
+            // evaluation.
+            continue;
+        }
+
+        auto const optional_is_auto_gen{is_auto_generated(col->get_namespace())};
+        if (false == optional_is_auto_gen.has_value()) {
+            // Ignore unrecognized namespace
+            continue;
+        }
+        auto& partial_resolutions{
+                optional_is_auto_gen.value() ? auto_gen_namespace_partial_resolutions
+                                             : user_gen_namespace_partial_resolutions
+        };
+
+        auto [it, inserted] = partial_resolutions.try_emplace(
+                SchemaTree::cRootId,
+                std::vector<QueryHandlerImpl::ColumnDescriptorTokenIterator>{}
+        );
+
+        auto const begin_token_it{
+                OUTCOME_TRYX(QueryHandlerImpl::ColumnDescriptorTokenIterator::create(col))
+        };
+        it->second.emplace_back(begin_token_it);
+        if (false == begin_token_it.is_last() && begin_token_it.is_wildcard()) {
+            // To handle the case where the prefix wildcard matches nothing
+            it->second.emplace_back(OUTCOME_TRYX(begin_token_it.next()));
+        }
+    }
+
+    return outcome_v2::success();
+}
+
+auto is_auto_generated(std::string_view key_namespace) -> std::optional<bool> {
+    if (clp_s::constants::cAutogenNamespace == key_namespace) {
+        return true;
+    }
+    if (clp_s::constants::cDefaultNamespace == key_namespace) {
+        return false;
+    }
+    return std::nullopt;
+}
+}  // namespace
+
+auto QueryHandlerImpl::create(
+        std::shared_ptr<Expression> query,
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections,
+        bool case_sensitive_match
+) -> outcome_v2::std_result<QueryHandlerImpl> {
+    query = OUTCOME_TRYX(preprocess_query(query));
+    auto [projected_columns, projected_column_to_original_key]
+            = OUTCOME_TRYX(create_projected_columns_and_projection_map(projections));
+    auto [auto_gen_namespace_partial_resolutions, user_gen_namespace_partial_resolutions]
+            = OUTCOME_TRYX(
+                    create_initial_partial_resolutions(query, projected_column_to_original_key)
+            );
+
+    return QueryHandlerImpl{
+            std::move(query),
+            std::move(auto_gen_namespace_partial_resolutions),
+            std::move(user_gen_namespace_partial_resolutions),
+            std::move(projected_columns),
+            std::move(projected_column_to_original_key),
+            case_sensitive_match
+    };
+}
+
+// TODO: Fix clang-tidy
+// NOLINTNEXTLINE(*)
+auto QueryHandlerImpl::evaluate_node_id_value_pairs(
+        [[maybe_unused]] KeyValuePairLogEvent::NodeIdValuePairs const& auto_gen_node_id_value_pairs,
+        [[maybe_unused]] KeyValuePairLogEvent::NodeIdValuePairs const& user_gen_node_id_value_pairs
+) -> outcome_v2::std_result<AstEvaluationResult> {
+    return ErrorCode{ErrorCodeEnum::MethodNotImplemented};
+}
+}  // namespace clp::ffi::ir_stream::search
diff --git a/components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp b/components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
new file mode 100644
index 0000000000..c809c1b42e
--- /dev/null
+++ b/components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
@@ -0,0 +1,352 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param column_descriptor
+         * @return A result containing the constructed token iterator on success, or an error code
+         * indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds if the column doesn't have any
+         *   tokens.
+         */
+        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* column_descriptor)
+                -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            auto const token_begin_it{column_descriptor->descriptor_begin()};
+            if (column_descriptor->descriptor_end() == token_begin_it) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{column_descriptor, token_begin_it};
+        }
+
+        // Methods
+        /**
+         * @return Whether this is the last token of the underlying column descriptor.
+         */
+        [[nodiscard]] auto is_last() const -> bool {
+            return m_column_descriptor->descriptor_end() == m_next_token_it;
+        }
+
+        /**
+         * @return A result containing a newly constructor iterator pointing to the next token on
+         * success, or an error code indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds if the current token is already
+         *   the last one.
+         */
+        [[nodiscard]] auto next() const -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            if (is_last()) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{m_column_descriptor, m_next_token_it};
+        }
+
+        [[nodiscard]] auto is_wildcard() const -> bool { return m_curr_token_it->wildcard(); }
+
+        [[nodiscard]] auto is_trailing_wildcard() const -> bool {
+            return is_last() && is_wildcard();
+        }
+
+        [[nodiscard]] auto get_token() const -> std::string_view {
+            return m_curr_token_it->get_token();
+        }
+
+        [[nodiscard]] auto get_column_descriptor() const -> clp_s::search::ast::ColumnDescriptor* {
+            return m_column_descriptor;
+        }
+
+        /**
+         * @param type
+         * @return Whether the underlying column can match the given schema-tree node type.
+         */
+        [[nodiscard]] auto match_schema_tree_node_type(SchemaTree::Node::Type type) const -> bool {
+            return m_column_descriptor->matches_any(schema_tree_node_type_to_literal_types(type));
+        }
+
+    private:
+        // Constructor
+        ColumnDescriptorTokenIterator(
+                clp_s::search::ast::ColumnDescriptor* column_descriptor,
+                clp_s::search::ast::DescriptorList::iterator curr_token_it
+        )
+                : m_column_descriptor{column_descriptor},
+                  m_curr_token_it{curr_token_it},
+                  m_next_token_it{curr_token_it + 1} {}
+
+        clp_s::search::ast::ColumnDescriptor* m_column_descriptor;
+        clp_s::search::ast::DescriptorList::iterator m_curr_token_it;
+        clp_s::search::ast::DescriptorList::iterator m_next_token_it;
+    };
+
+    using ProjectionMap = std::unordered_map<clp_s::search::ast::ColumnDescriptor*, std::string>;
+
+    using PartialResolutionMap = std::
+            unordered_map<SchemaTree::Node::id_t, std::vector<ColumnDescriptorTokenIterator>>;
+
+    // Factory function
+    /**
+     * @param query The search query.
+     * @param projections The columns to project.
+     * @param case_sensitive_match Whether to use case-sensitive match for string comparison.
+     * @return A result containing the newly constructed `QueryHandler` on success, or an error code
+     * indicating the failure:
+     * - Forwards `preprocess_query`'s return values.
+     * - Forwards `create_projected_columns_and_projection_map`'s return values.
+     * - Forwards `create_initial_partial_resolutions`'s return values.
+     */
+    [[nodiscard]] static auto create(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            std::vector<std::pair<std::string, clp_s::search::ast::LiteralTypeBitmask>> const&
+                    projections,
+            bool case_sensitive_match
+    ) -> outcome_v2::std_result<QueryHandlerImpl>;
+
+    // Delete copy constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl const&) = delete;
+    auto operator=(QueryHandlerImpl const&) -> QueryHandlerImpl& = delete;
+
+    // Default move constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl&&) = default;
+    auto operator=(QueryHandlerImpl&&) -> QueryHandlerImpl& = default;
+
+    // Destructor
+    ~QueryHandlerImpl() = default;
+
+    /**
+     * Implementation of `QueryHandler::evaluate_node_id_value_pairs`.
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - TODO
+     */
+    [[nodiscard]] auto evaluate_node_id_value_pairs(
+            KeyValuePairLogEvent::NodeIdValuePairs const& auto_gen_node_id_value_pairs,
+            KeyValuePairLogEvent::NodeIdValuePairs const& user_gen_node_id_value_pairs
+    ) -> outcome_v2::std_result<AstEvaluationResult>;
+
+    /**
+     * Implementation of `QueryHandler::update_partially_resolved_columns` with new projected
+     * schema-tree node callback given as a template parameter.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @param new_projected_schema_tree_node_callback
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `handle_column_resolution_on_new_schema_tree_node`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto update_partially_resolved_columns(
+            bool is_auto_generated,
+            SchemaTree::NodeLocator const& node_locator,
+            SchemaTree::Node::id_t node_id,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    [[nodiscard]] auto get_resolved_column_to_schema_tree_node_ids() const -> std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>> {
+        return m_resolved_column_to_schema_tree_node_ids;
+    }
+
+private:
+    // Constructor
+    QueryHandlerImpl(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            PartialResolutionMap auto_gen_namespace_partial_resolutions,
+            PartialResolutionMap user_gen_namespace_partial_resolutions,
+            std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> projected_columns,
+            ProjectionMap projected_column_to_original_key,
+            bool case_sensitive_match
+    )
+            : m_query{std::move(query)},
+              m_auto_gen_namespace_partial_resolutions{
+                      std::move(auto_gen_namespace_partial_resolutions)
+              },
+              m_user_gen_namespace_partial_resolutions{
+                      std::move(user_gen_namespace_partial_resolutions)
+              },
+              m_projected_columns{std::move(projected_columns)},
+              m_projected_column_to_original_key{std::move(projected_column_to_original_key)},
+              m_case_sensitive_search{case_sensitive_match} {}
+
+    // Methods
+    /**
+     * Handles column resolution of the given token iterator on the a new schema-tree tree node.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param is_auto_generated
+     * @param node_id
+     * @param node_locator
+     * @param token_it The current resolved token in the column.
+     * @param new_projected_schema_tree_node_callback
+     * @return A void result on success, or an error code indicating the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `new_projected_schema_tree_node_callback`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto handle_column_resolution_on_new_schema_tree_node(
+            bool is_auto_generated,
+            SchemaTree::Node::id_t node_id,
+            SchemaTree::NodeLocator const& node_locator,
+            ColumnDescriptorTokenIterator const& token_it,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    // Variables
+    std::shared_ptr<clp_s::search::ast::Expression> m_query;
+    PartialResolutionMap m_auto_gen_namespace_partial_resolutions;
+    PartialResolutionMap m_user_gen_namespace_partial_resolutions;
+    std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>>
+            m_resolved_column_to_schema_tree_node_ids;
+    std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> m_projected_columns;
+    ProjectionMap m_projected_column_to_original_key;
+    bool m_case_sensitive_search;
+};
+
+template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+auto QueryHandlerImpl::update_partially_resolved_columns(
+        bool is_auto_generated,
+        SchemaTree::NodeLocator const& node_locator,
+        SchemaTree::Node::id_t node_id,
+        NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+) -> outcome_v2::std_result<void> {
+    auto const parent_node_id{node_locator.get_parent_id()};
+    auto& partial_resolutions_to_update{
+            is_auto_generated ? m_auto_gen_namespace_partial_resolutions
+                              : m_user_gen_namespace_partial_resolutions
+    };
+    if (false == partial_resolutions_to_update.contains(parent_node_id)) {
+        return outcome_v2::success();
+    }
+
+    std::vector<std::pair<SchemaTree::Node::id_t, ColumnDescriptorTokenIterator>>
+            new_partial_resolutions;
+    for (auto const& token_it : partial_resolutions_to_update.at(parent_node_id)) {
+        OUTCOME_TRYV(handle_column_resolution_on_new_schema_tree_node(
+                is_auto_generated,
+                node_id,
+                node_locator,
+                token_it,
+                new_projected_schema_tree_node_callback
+        ));
+
+        // Handle partial resolution updates
+        if (SchemaTree::Node::Type::Obj != node_locator.get_type()) {
+            // If the schema-tree node is not of type `Obj`, it cannot have children, so no further
+            // resolution is possible from this node.
+            continue;
+        }
+
+        if (token_it.is_wildcard()) {
+            // For wildcard tokens, simulate potential matches by:
+            // - Adding a resolution that assumes this node matches part of the wildcard.
+            // - If this isn't the last wildcard token, also add a resolution for continuing the
+            //   match.
+            new_partial_resolutions.emplace_back(node_id, token_it);
+            if (false == token_it.is_last()) {
+                new_partial_resolutions.emplace_back(node_id, OUTCOME_TRYX(token_it.next()));
+            }
+            continue;
+        }
+
+        if (token_it.is_last() || token_it.get_token() != node_locator.get_key_name()) {
+            // If this is the last token, or the token doesn't match the node's key, then no further
+            // resolution is possible through the given node.
+            continue;
+        }
+
+        auto const next_token_it{OUTCOME_TRYX(token_it.next())};
+        new_partial_resolutions.emplace_back(node_id, next_token_it);
+        if (false == next_token_it.is_last() && next_token_it.is_wildcard()) {
+            // Handle the case where the wildcard matches nothing
+            new_partial_resolutions.emplace_back(node_id, OUTCOME_TRYX(next_token_it.next()));
+        }
+    }
+
+    for (auto const [node_id, token_it] : new_partial_resolutions) {
+        auto [it, inserted] = partial_resolutions_to_update.try_emplace(
+                node_id,
+                std::vector<ColumnDescriptorTokenIterator>{}
+        );
+        it->second.emplace_back(token_it);
+    }
+
+    return outcome_v2::success();
+}
+
+template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+auto QueryHandlerImpl::handle_column_resolution_on_new_schema_tree_node(
+        bool is_auto_generated,
+        SchemaTree::Node::id_t node_id,
+        SchemaTree::NodeLocator const& node_locator,
+        QueryHandlerImpl::ColumnDescriptorTokenIterator const& token_it,
+        NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+) -> outcome_v2::std_result<void> {
+    if ((false == token_it.is_last()
+         && false == OUTCOME_TRYX(token_it.next()).is_trailing_wildcard())
+        || false == token_it.match_schema_tree_node_type(node_locator.get_type())
+        || (false == token_it.is_wildcard() && token_it.get_token() != node_locator.get_key_name()))
+    {
+        // If the given token:
+        // - is neither the end token nor the last token before a trailing wildcard
+        // - doesn't match the new node's type
+        // - is neither a wildcard nor equal to the new node's key
+        // There should be no resolution.
+        return outcome_v2::success();
+    }
+
+    auto* col{token_it.get_column_descriptor()};
+    if (m_projected_column_to_original_key.contains(col)) {
+        OUTCOME_TRYV(new_projected_schema_tree_node_callback(
+                is_auto_generated,
+                node_id,
+                m_projected_column_to_original_key.at(col)
+        ));
+        return outcome_v2::success();
+    }
+
+    auto [it, inserted] = m_resolved_column_to_schema_tree_node_ids.try_emplace(
+            col,
+            std::unordered_set<SchemaTree::Node::id_t>{}
+    );
+    it->second.emplace(node_id);
+    return outcome_v2::success();
+}
+}  // namespace clp::ffi::ir_stream::search
+
+#endif  // CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
diff --git a/components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp b/components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp
new file mode 100644
index 0000000000..1b6d522c2d
--- /dev/null
+++ b/components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp
@@ -0,0 +1,451 @@
+#include <map>
+#include <memory>
+#include <set>
+#include <sstream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <catch2/catch.hpp>
+#include <fmt/core.h>
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/archive_constants.hpp"
+#include "../../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../../clp_s/search/kql/kql.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../../../Value.hpp"
+#include "../QueryHandlerImpl.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::constants::cAutogenNamespace;
+using clp_s::constants::cDefaultNamespace;
+using clp_s::constants::cReservedNamespace1;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+constexpr std::string_view cRefTestStr{"*test*"};
+constexpr value_int_t cRefTestInt{0};
+constexpr value_float_t cRefTestFloat{0.0};
+constexpr value_bool_t cRefTestBool{false};
+
+/**
+ * Generates all the matchable KQL expressions based on the column queries and their matchable
+ * types. For each column query `C` and matched type `T`, we generate an expression of
+ * `C: cRefTestT`, where `cRefTestT` is any of the constants defined above.
+ * @param column_namespace The namespace of the column.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - A vector of matchable KQL expressions.
+ * - A map of expected column resolutions.
+ */
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param column_namespace The namespace of the column.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - Generated projections as pairs, where each pair consists of:
+ *   - A projectable column from `column_query_to_possible_matches` that has no wildcard.
+ *   - All the matchable types of this column.
+ * - A map of expected resolved projections.
+ */
+[[nodiscard]] auto generate_projections(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+)
+        -> std::pair<
+                std::vector<std::pair<std::string, LiteralTypeBitmask>>,
+                std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param column_resolutions
+ * @return The serialized pairs.
+ */
+[[nodiscard]] auto serialize_column_node_ids_map(
+        std::map<std::string, std::set<SchemaTree::Node::id_t>> const& column_resolutions
+) -> std::string;
+
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>> {
+    std::vector<std::string> matchable_kql_expressions;
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> expected_column_resolutions;
+    for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
+        auto const column_query_with_namespace{fmt::format("{}{}", column_namespace, column_query)};
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Int
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestInt)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Float
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {:.2f}", column_query_with_namespace, cRefTestFloat)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Bool
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestBool)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Str
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestStr)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+    }
+
+    auto const single_wildcard{fmt::format("{}{}", column_namespace, "*")};
+    if (expected_column_resolutions.contains(single_wildcard)) {
+        // NOTE: The current implementation doesn't resolve single wildcard.
+        expected_column_resolutions.erase(single_wildcard);
+    }
+    return {std::move(matchable_kql_expressions), std::move(expected_column_resolutions)};
+}
+
+auto generate_projections(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+)
+        -> std::pair<
+                std::vector<std::pair<std::string, LiteralTypeBitmask>>,
+                std::map<std::string, std::set<SchemaTree::Node::id_t>>> {
+    std::vector<std::pair<std::string, LiteralTypeBitmask>> projections;
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> expected_resolved_projections;
+    for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
+        if (column_query.find('*') != std::string::npos) {
+            continue;
+        }
+        auto const projectable_node_ids{possible_matches.get_matchable_node_ids()};
+        REQUIRE_FALSE(projectable_node_ids.empty());
+        auto const column_query_with_namespace{fmt::format("{}{}", column_namespace, column_query)};
+        projections.emplace_back(
+                column_query_with_namespace,
+                possible_matches.get_matchable_types()
+        );
+        expected_resolved_projections.emplace(
+                column_query_with_namespace,
+                std::set<SchemaTree::Node::id_t>{
+                        projectable_node_ids.cbegin(),
+                        projectable_node_ids.cend()
+                }
+        );
+    }
+    return {std::move(projections), std::move(expected_resolved_projections)};
+}
+
+auto serialize_column_node_ids_map(
+        std::map<std::string, std::set<SchemaTree::Node::id_t>> const& column_resolutions
+) -> std::string {
+    std::string result;
+    for (auto const& [column, resolved_node_ids] : column_resolutions) {
+        result += fmt::format("{}: {}\n", column, fmt::join(resolved_node_ids, ","));
+    }
+    return result;
+}
+}  // namespace
+
+TEST_CASE(
+        "query_handler_update_partially_resolved_columns",
+        "[ffi][ir_stream][search][QueryHandler]"
+) {
+    /*
+     * <0:root:Obj>
+     *      |
+     *      |------------> <1:a:Obj>
+     *      |                  |
+     *      |--> <2:b:Int>     |--> <3:b:Obj>
+     *      |                  |        |
+     *      |--> <12:a:Int>    |        |------------> <4:c:Obj>
+     *                         |        |                  |
+     *                         |        |--> <5:d:Str>     |--> <7:a:Str>
+     *                         |        |                  |
+     *                         |        |--> <6:d:Bool>    |--> <8:d:Str>
+     *                         |        |                  |
+     *                         |        |--> <10:e:Obj>    |--> <9:d:Float>
+     *                         |                           |
+     *                         |--> <13:b:Bool>            |--> <11:f:Obj>
+     */
+    auto const schema_tree{std::make_shared<SchemaTree>()};
+    std::vector<SchemaTree::NodeLocator> const locators{
+            {SchemaTree::cRootId, "a", SchemaTree::Node::Type::Obj},
+            {SchemaTree::cRootId, "b", SchemaTree::Node::Type::Int},
+            {1, "b", SchemaTree::Node::Type::Obj},
+            {3, "c", SchemaTree::Node::Type::Obj},
+            {3, "d", SchemaTree::Node::Type::Str},
+            {3, "d", SchemaTree::Node::Type::Bool},
+            {4, "a", SchemaTree::Node::Type::Str},
+            {4, "d", SchemaTree::Node::Type::Str},
+            {4, "d", SchemaTree::Node::Type::Float},
+            {3, "e", SchemaTree::Node::Type::Obj},
+            {4, "f", SchemaTree::Node::Type::Obj},
+            {SchemaTree::cRootId, "a", SchemaTree::Node::Type::Int},
+            {1, "b", SchemaTree::Node::Type::Bool}
+    };
+    for (auto const& locator : locators) {
+        REQUIRE_NOTHROW(schema_tree->insert_node(locator));
+    }
+
+    auto const column_query_to_possible_matches{get_schema_tree_column_queries(schema_tree)};
+    CAPTURE(column_query_to_possible_matches);
+
+    auto const is_auto_generated = GENERATE(true, false);
+    auto const [matchable_kql_expressions, expected_column_resolutions]
+            = generate_matchable_kql_expressions(
+                    is_auto_generated ? cAutogenNamespace : cDefaultNamespace,
+                    column_query_to_possible_matches
+            );
+    auto const matchable_kql_expressions_with_unrecognized_namespace{
+            generate_matchable_kql_expressions(
+                    cReservedNamespace1,
+                    column_query_to_possible_matches
+            )
+                    .first
+    };
+
+    constexpr std::string_view cUnmatchableNodeName{"unknown"};
+    for (auto const& locator : locators) {
+        REQUIRE((locator.get_key_name() != cUnmatchableNodeName));
+    }
+    std::vector<std::string> unmatchable_kql_expressions;
+    for (auto const& [matchable_column, resolved_node_ids] : expected_column_resolutions) {
+        unmatchable_kql_expressions.emplace_back(
+                fmt::format("{}.{}: {}", matchable_column, cUnmatchableNodeName, cRefTestInt)
+        );
+    }
+
+    auto const matchable_kql_query_str{
+            fmt::format("{}", fmt::join(matchable_kql_expressions, " OR "))
+    };
+    auto const unmatchable_kql_query_str{
+            fmt::format("{}", fmt::join(unmatchable_kql_expressions, " OR "))
+    };
+    auto const unrecognized_namespace_kql_query_str{fmt::format(
+            "{}",
+            fmt::join(matchable_kql_expressions_with_unrecognized_namespace, " OR ")
+    )};
+    auto const kql_query_str{fmt::format(
+            "{} OR {} OR {}",
+            matchable_kql_query_str,
+            unmatchable_kql_query_str,
+            unrecognized_namespace_kql_query_str
+    )};
+    CAPTURE(kql_query_str);
+
+    auto query_stream{std::istringstream{kql_query_str}};
+    auto query{clp_s::search::kql::parse_kql_expression(query_stream)};
+
+    auto query_handler_impl_result{QueryHandlerImpl::create(query, {}, true)};
+    REQUIRE_FALSE(query_handler_impl_result.has_error());
+    auto& query_handler_impl{query_handler_impl_result.value()};
+
+    SchemaTree::Node::id_t node_id{1};
+    for (auto const& locator : locators) {
+        REQUIRE_FALSE(query_handler_impl
+                              .update_partially_resolved_columns(
+                                      true,
+                                      locator,
+                                      node_id,
+                                      trivial_new_projected_schema_tree_node_callback
+                              )
+                              .has_error());
+        REQUIRE_FALSE(query_handler_impl
+                              .update_partially_resolved_columns(
+                                      false,
+                                      locator,
+                                      node_id,
+                                      trivial_new_projected_schema_tree_node_callback
+                              )
+                              .has_error());
+        ++node_id;
+    }
+
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> actual_column_resolutions;
+    for (auto const& [column_descriptor, resolved_nodes] :
+         query_handler_impl.get_resolved_column_to_schema_tree_node_ids())
+    {
+        REQUIRE(
+                (column_descriptor->get_namespace() == cDefaultNamespace
+                 || column_descriptor->get_namespace() == cAutogenNamespace)
+        );
+        std::vector<std::string_view> tokens;
+        for (auto const& token : column_descriptor->get_descriptor_list()) {
+            tokens.emplace_back(token.get_token());
+        }
+        auto const original_column_query{
+                fmt::format("{}{}", column_descriptor->get_namespace(), fmt::join(tokens, "."))
+        };
+        auto [it, inserted] = actual_column_resolutions.try_emplace(
+                original_column_query,
+                std::set<SchemaTree::Node::id_t>{}
+        );
+        auto& all_resolved_nodes{it->second};
+        all_resolved_nodes.insert(resolved_nodes.cbegin(), resolved_nodes.cend());
+    }
+
+    CAPTURE("\n" + serialize_column_node_ids_map(actual_column_resolutions));
+    CAPTURE("\n" + serialize_column_node_ids_map(expected_column_resolutions));
+
+    REQUIRE((actual_column_resolutions == expected_column_resolutions));
+}
+
+TEST_CASE("query_handler_handle_projection", "[ffi][ir_stream][search][QueryHandler]") {
+    /*
+     * <0:root:Obj>
+     *      |
+     *      |------------> <1:a:Obj>
+     *      |                  |
+     *      |--> <2:b:Int>     |--> <3:b:Obj>
+     *      |                  |        |
+     *      |--> <12:a:Int>    |        |------------> <4:c:Obj>
+     *                         |        |                  |
+     *                         |        |--> <5:d:Str>     |--> <7:a:Str>
+     *                         |        |                  |
+     *                         |        |--> <6:d:Bool>    |--> <8:d:Str>
+     *                         |        |                  |
+     *                         |        |--> <10:e:Obj>    |--> <9:d:Float>
+     *                         |                           |
+     *                         |--> <13:b:Bool>            |--> <11:f:Obj>
+     */
+    auto const schema_tree{std::make_shared<SchemaTree>()};
+    std::vector<SchemaTree::NodeLocator> const locators{
+            {SchemaTree::cRootId, "a", SchemaTree::Node::Type::Obj},
+            {SchemaTree::cRootId, "b", SchemaTree::Node::Type::Int},
+            {1, "b", SchemaTree::Node::Type::Obj},
+            {3, "c", SchemaTree::Node::Type::Obj},
+            {3, "d", SchemaTree::Node::Type::Str},
+            {3, "d", SchemaTree::Node::Type::Bool},
+            {4, "a", SchemaTree::Node::Type::Str},
+            {4, "d", SchemaTree::Node::Type::Str},
+            {4, "d", SchemaTree::Node::Type::Float},
+            {3, "e", SchemaTree::Node::Type::Obj},
+            {4, "f", SchemaTree::Node::Type::Obj},
+            {SchemaTree::cRootId, "a", SchemaTree::Node::Type::Int},
+            {1, "b", SchemaTree::Node::Type::Bool}
+    };
+    for (auto const& locator : locators) {
+        REQUIRE_NOTHROW(schema_tree->insert_node(locator));
+    }
+
+    auto const column_query_to_possible_matches{get_schema_tree_column_queries(schema_tree)};
+    CAPTURE(column_query_to_possible_matches);
+
+    auto const is_auto_generated = GENERATE(true, false);
+    auto const column_namespace{is_auto_generated ? cAutogenNamespace : cDefaultNamespace};
+    auto const [resolvable_projections, expected_resolved_projections]
+            = generate_projections(column_namespace, column_query_to_possible_matches);
+    auto const unresolvable_projections_from_unrecognized_namespaces{
+            generate_projections(cReservedNamespace1, column_query_to_possible_matches).first
+    };
+    auto empty_query = clp_s::search::ast::EmptyExpr::create();
+
+    auto projections{resolvable_projections};
+    projections.insert(
+            projections.end(),
+            unresolvable_projections_from_unrecognized_namespaces.cbegin(),
+            unresolvable_projections_from_unrecognized_namespaces.cend()
+    );
+
+    auto query_handler_impl_result{QueryHandlerImpl::create(empty_query, projections, true)};
+    REQUIRE_FALSE(query_handler_impl_result.has_error());
+    auto& query_handler_impl{query_handler_impl_result.value()};
+
+    SchemaTree::Node::id_t node_id{1};
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> actual_resolved_projections;
+    auto new_projected_schema_tree_node_callback
+            = [&](bool is_auto_gen, SchemaTree::Node::id_t node_id, std::string_view key
+              ) -> outcome_v2::std_result<void> {
+        REQUIRE((is_auto_generated == is_auto_gen));
+        auto [column_it, column_inserted] = actual_resolved_projections.try_emplace(
+                std::string{key},
+                std::set<SchemaTree::Node::id_t>{}
+        );
+        auto [node_id_it, node_id_inserted] = column_it->second.emplace(node_id);
+        REQUIRE(node_id_inserted);
+        return outcome_v2::success();
+    };
+    for (auto const& locator : locators) {
+        REQUIRE_FALSE(query_handler_impl
+                              .update_partially_resolved_columns(
+                                      true,
+                                      locator,
+                                      node_id,
+                                      new_projected_schema_tree_node_callback
+                              )
+                              .has_error());
+        REQUIRE_FALSE(query_handler_impl
+                              .update_partially_resolved_columns(
+                                      false,
+                                      locator,
+                                      node_id,
+                                      new_projected_schema_tree_node_callback
+                              )
+                              .has_error());
+        ++node_id;
+    }
+
+    CAPTURE("\n" + serialize_column_node_ids_map(actual_resolved_projections));
+    CAPTURE("\n" + serialize_column_node_ids_map(expected_resolved_projections));
+
+    REQUIRE((expected_resolved_projections == actual_resolved_projections));
+}
+}  // namespace clp::ffi::ir_stream::search::test
diff --git a/components/core/src/clp/ffi/ir_stream/search/test/utils.cpp b/components/core/src/clp/ffi/ir_stream/search/test/utils.cpp
new file mode 100644
index 0000000000..7cac925131
--- /dev/null
+++ b/components/core/src/clp/ffi/ir_stream/search/test/utils.cpp
@@ -0,0 +1,153 @@
+#include "utils.hpp"
+
+#include <bitset>
+#include <cstddef>
+#include <map>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <fmt/core.h>
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../SchemaTree.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::search::ast::LiteralType;
+}  // namespace
+
+auto ColumnQueryPossibleMatches::get_matchable_node_ids_from_literal_type(LiteralType type) const
+        -> std::vector<SchemaTree::Node::id_t> {
+    if ((type & m_matchable_types) == 0) {
+        return {};
+    }
+
+    switch (type) {
+        case LiteralType::IntegerT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Int);
+        case LiteralType::FloatT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Float);
+        case LiteralType::BooleanT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Bool);
+        case LiteralType::VarStringT:
+        case LiteralType::ClpStringT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Str);
+        default:
+            return {};
+    }
+}
+
+auto ColumnQueryPossibleMatches::get_matchable_node_ids_from_schema_tree_type(
+        SchemaTree::Node::Type type
+) const -> std::vector<SchemaTree::Node::id_t> {
+    std::vector<SchemaTree::Node::id_t> node_ids;
+    for (auto const node_id : m_matchable_node_ids) {
+        if (m_schema_tree->get_node(node_id).get_type() == type) {
+            node_ids.emplace_back(node_id);
+        }
+    }
+    return node_ids;
+}
+
+auto ColumnQueryPossibleMatches::serialize() const -> std::string {
+    constexpr size_t cBitsetSize{sizeof(m_matchable_types) * 8};
+    return fmt::format(
+            "Types: {}; Node IDs: {{{}}}",
+            std::bitset<cBitsetSize>(m_matchable_types).to_string(),
+            fmt::join(m_matchable_node_ids, ",")
+    );
+}
+
+auto trivial_new_projected_schema_tree_node_callback(
+        [[maybe_unused]] bool is_auto_generated,
+        [[maybe_unused]] SchemaTree::Node::id_t node_id,
+        [[maybe_unused]] std::string_view projected_key_path
+) -> outcome_v2::std_result<void> {
+    return outcome_v2::success();
+}
+
+auto get_schema_tree_column_queries(std::shared_ptr<SchemaTree> const& schema_tree)
+        -> std::map<std::string, ColumnQueryPossibleMatches> {
+    std::map<std::string, ColumnQueryPossibleMatches> column_query_to_possible_matches;
+    std::vector<std::pair<std::string, SchemaTree::Node::id_t>> search_stack;
+
+    auto push_all_children_to_stack
+            = [&](SchemaTree::Node const& node, std::string const& query) -> void {
+        for (auto const node_id : node.get_children_ids()) {
+            search_stack.emplace_back(query, node_id);
+        }
+    };
+
+    push_all_children_to_stack(schema_tree->get_root(), "");
+    push_all_children_to_stack(schema_tree->get_root(), "*");
+
+    // For every node, there are two possible input column queries:
+    // 1. [...].[*]
+    // 2. [...].[parent_key]
+    // Based on this input, there are five valid column queries for each node:
+    // 1. [...].[*]
+    // 2. [...].[*].[curr_key]
+    // 3. [...].[*].[curr_key].[*]
+    // 4. [...].[parent_key].[curr_key]
+    // 5. [...].[parent_key].[curr_key].[*]
+
+    while (false == search_stack.empty()) {
+        auto const [parent_query, node_id] = search_stack.back();
+        search_stack.pop_back();
+
+        auto const& node{schema_tree->get_node(node_id)};
+        bool const is_parent_query_empty{parent_query.empty()};
+        bool const parent_query_end_with_wildcard{
+                is_parent_query_empty ? false : parent_query.back() == '*'
+        };
+
+        if (parent_query_end_with_wildcard) {
+            // Handle the trailing wildcard from the parent node
+            auto [it, inserted] = column_query_to_possible_matches.try_emplace(
+                    parent_query,
+                    ColumnQueryPossibleMatches{schema_tree}
+            );
+            it->second.set_matchable_node(node_id, node.get_type());
+            push_all_children_to_stack(node, parent_query);
+        }
+
+        auto const query_with_curr_key{
+                parent_query + (is_parent_query_empty ? "" : ".") + std::string{node.get_key_name()}
+        };
+        auto [without_wildcard_it, without_wildcard_inserted]
+                = column_query_to_possible_matches.try_emplace(
+                        query_with_curr_key,
+                        ColumnQueryPossibleMatches{schema_tree}
+                );
+        without_wildcard_it->second.set_matchable_node(node_id, node.get_type());
+        push_all_children_to_stack(node, query_with_curr_key);
+
+        auto const query_with_trailing_wildcard{query_with_curr_key + ".*"};
+        auto [with_wildcard_it, with_wildcard_inserted]
+                = column_query_to_possible_matches.try_emplace(
+                        query_with_trailing_wildcard,
+                        ColumnQueryPossibleMatches{schema_tree}
+                );
+        with_wildcard_it->second.set_matchable_node(node_id, node.get_type());
+        push_all_children_to_stack(node, query_with_trailing_wildcard);
+    }
+
+    return column_query_to_possible_matches;
+}
+
+auto operator<<(
+        std::ostream& os,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::ostream& {
+    for (auto const& [query, possible_matches] : column_query_to_possible_matches) {
+        os << fmt::format("Query: {}; {}\n", query, possible_matches.serialize());
+    }
+    return os;
+}
+}  // namespace clp::ffi::ir_stream::search::test
diff --git a/components/core/src/clp/ffi/ir_stream/search/test/utils.hpp b/components/core/src/clp/ffi/ir_stream/search/test/utils.hpp
new file mode 100644
index 0000000000..6c6dc85d86
--- /dev/null
+++ b/components/core/src/clp/ffi/ir_stream/search/test/utils.hpp
@@ -0,0 +1,91 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+
+#include <map>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+/**
+ * Represent all the matchable clp-s literal types and the matchable schema-tree node IDs of a
+ * column query.
+ */
+class ColumnQueryPossibleMatches {
+public:
+    explicit ColumnQueryPossibleMatches(std::shared_ptr<SchemaTree> schema_tree)
+            : m_schema_tree{std::move(schema_tree)} {}
+
+    [[nodiscard]] auto get_matchable_types() const -> clp_s::search::ast::LiteralTypeBitmask {
+        return m_matchable_types;
+    }
+
+    [[nodiscard]] auto get_matchable_node_ids() const -> std::set<SchemaTree::Node::id_t> const& {
+        return m_matchable_node_ids;
+    }
+
+    [[nodiscard]] auto get_matchable_node_ids_from_literal_type(
+            clp_s::search::ast::LiteralType type
+    ) const -> std::vector<SchemaTree::Node::id_t>;
+
+    [[nodiscard]] auto get_matchable_node_ids_from_schema_tree_type(
+            SchemaTree::Node::Type type
+    ) const -> std::vector<SchemaTree::Node::id_t>;
+
+    auto set_matchable_node(SchemaTree::Node::id_t node_id, SchemaTree::Node::Type type) -> void {
+        m_matchable_node_ids.emplace(node_id);
+        m_matchable_types |= schema_tree_node_type_to_literal_types(type);
+    }
+
+    /**
+     * Serializes the underlying matchable types and matchable node IDs in human-readable form for
+     * debugging purposes.
+     * @return The serialized possible matches.
+     */
+    [[nodiscard]] auto serialize() const -> std::string;
+
+private:
+    std::shared_ptr<SchemaTree> m_schema_tree;
+    clp_s::search::ast::LiteralTypeBitmask m_matchable_types{};
+    std::set<SchemaTree::Node::id_t> m_matchable_node_ids;
+};
+
+/**
+ * Trivial implementation of `NewProjectedSchemaTreeNodeCallback` that always return success without
+ * doing anything.
+ * @param is_auto_generated
+ * @param
+ */
+[[nodiscard]] auto trivial_new_projected_schema_tree_node_callback(
+        bool is_auto_generated,
+        SchemaTree::Node::id_t node_id,
+        std::string_view projected_key_path
+) -> outcome_v2::std_result<void>;
+
+/**
+ * Gets all possible column queries to every single node in the schema-tree with a bitmask
+ * indicating all the potentially matched types.
+ * NOTE: It is assumed that all the keys in the schema-tree to test don't contain escaped chars.
+ * @param schema_tree
+ * @return A column-query-to-possible-matches map.
+ */
+[[nodiscard]] auto get_schema_tree_column_queries(std::shared_ptr<SchemaTree> const& schema_tree)
+        -> std::map<std::string, ColumnQueryPossibleMatches>;
+
+[[maybe_unused]] auto operator<<(
+        std::ostream& os,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::ostream&;
+}  // namespace clp::ffi::ir_stream::search::test
+
+#endif  // CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP


Reviews:
user: login: LinZhihao-723
body: - Still waiting for #856 to merge, but the major logic should be already included in this PR.
- Still need to work on unit tests.

@gibber9809 I think what's already included so far should be ready for review.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-01T00:01:54Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (7)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/ErrorCode.hpp (1)</summary><blockquote>

`13-22`: **Consider future-proofing the enum size**

`ErrorCodeEnum` is constrained to `uint8_t`.  We now have 9 values; still safe, but adding more than 256 codes in the long run will silently wrap.  If you expect the set to keep expanding, consider switching to `uint16_t` early to avoid a breaking change later.

</blockquote></details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp (1)</summary><blockquote>

`31-33`: **Iterator class lacks `operator== / operator!=`**

Providing comparison operators would make the iterator composable with STL algorithms (and future-proofs the design).

</blockquote></details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp (5)</summary><blockquote>

`113-136`: **Good use of error handling, but the exception catch is broad**

Your implementation properly checks for null query and follows the code style of using `nullptr == query` instead of `!query` as per guidelines. However, the catch block at line 133 catches all exceptions and returns a generic error code. Consider catching specific exceptions or at least logging the exception message to aid in debugging.


```diff
    } catch (std::exception const& ex) {
-        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+        // Log or capture exception details for debugging
+        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed, ex.what()};
```

---

`148-164`: **The duplicate check could be combined with try-emplace**

The current implementation uses a separate set to track duplicates, but this could be streamlined using the return value of try_emplace.


```diff
-    std::unordered_set<std::string_view> unique_projected_columns;

     for (auto const& [key, types] : projections) {
-        if (unique_projected_columns.contains(key)) {
-            return ErrorCode{ErrorCodeEnum::DuplicateProjectedColumn};
-        }
-        unique_projected_columns.emplace(key);
```

This is only a suggestion since the current approach with a separate set might be clearer and more maintainable in some contexts.

---

`166-182`: **Consider separating exception handling for different operations**

The current implementation has a single try-catch block that encompasses multiple operations: creating a column descriptor, setting matching types, and checking validity. This makes it difficult to provide specific error messages for different types of failures.


Consider separating the try-catch blocks to handle different operations separately, allowing for more specific error codes or messages.

---

`202-207`: **Map insertion could be optimized**

The code checks if a key exists before inserting, which is an extra lookup operation. Consider using `try_emplace` directly, which will only insert if the key doesn't exist.


```diff
-        if (false == partial_resolutions.contains(SchemaTree::cRootId)) {
-            partial_resolutions.emplace(
-                    SchemaTree::cRootId,
-                    std::vector<QueryHandlerImpl::ColumnDescriptorTokenIterator>{}
-            );
-        }
+        auto [it, inserted] = partial_resolutions.try_emplace(
+                SchemaTree::cRootId,
+                std::vector<QueryHandlerImpl::ColumnDescriptorTokenIterator>{}
+        );
```

This approach is actually used later in the code at line 262, so this would make the code more consistent.

---

`256-260`: **Code reuse opportunity for namespace resolution logic**

This code duplicates the logic from lines 197-201. Consider extracting this pattern into a small helper function since it's used multiple times.


```diff
+    auto& get_resolution_map_for_namespace(
+            std::string_view ns,
+            QueryHandlerImpl::PartialResolutionMap& auto_gen,
+            QueryHandlerImpl::PartialResolutionMap& user_gen) {
+        return (OUTCOME_TRYX(is_auto_generated(ns))) ? auto_gen : user_gen;
+    }

// Then use it like:
-        bool const is_auto_gen{OUTCOME_TRYX(is_auto_generated(col->get_namespace()))};
-        auto& partial_resolutions{
-                is_auto_gen ? auto_gen_namespace_partial_resolutions
-                            : user_gen_namespace_partial_resolutions
-        };
+        auto& partial_resolutions = get_resolution_map_for_namespace(
+                col->get_namespace(),
+                auto_gen_namespace_partial_resolutions,
+                user_gen_namespace_partial_resolutions);
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 1f70d17463db65785ceb36eca8b8794e8953e81c and 411bb91a1572c10bfee6e3534237fc5dcfe43cd4.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `components/core/CMakeLists.txt` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/ErrorCode.cpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/ErrorCode.hpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (1)</summary>

<details>
<summary>`**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.</summary>

> `**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.

- `components/core/src/clp/ffi/ir_stream/search/ErrorCode.cpp`
- `components/core/src/clp/ffi/ir_stream/search/ErrorCode.hpp`
- `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp`
- `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp`

</details>

</details><details>
<summary>🧬 Code Graph Analysis (2)</summary>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp (1)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp (9)</summary>

* `nodiscard` (43-50)
* `nodiscard` (56-58)
* `nodiscard` (66-71)
* `nodiscard` (73-73)
* `nodiscard` (75-77)
* `nodiscard` (79-81)
* `nodiscard` (89-92)
* `query` (125-130)
* `auto_gen_node_id_value_pairs` (151-154)

</details>

</blockquote></details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp (2)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp (3)</summary>

* `is_auto_generated` (110-111)
* `is_auto_generated` (280-288)
* `is_auto_generated` (280-280)

</details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandler.hpp (1)</summary>

* `new_projected_schema_tree_node_callback` (33-35)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (11)</summary>

* GitHub Check: centos-stream-9-static-linked-bins
* GitHub Check: ubuntu-jammy-static-linked-bins
* GitHub Check: ubuntu-jammy-lint
* GitHub Check: centos-stream-9-dynamic-linked-bins
* GitHub Check: ubuntu-jammy-dynamic-linked-bins
* GitHub Check: build-macos (macos-15, true)
* GitHub Check: build-macos (macos-15, false)
* GitHub Check: build-macos (macos-13, false)
* GitHub Check: build-macos (macos-13, true)
* GitHub Check: lint-check (ubuntu-latest)
* GitHub Check: lint-check (macos-latest)

</details>

<details>
<summary>🔇 Additional comments (9)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/ErrorCode.cpp (1)</summary>

`20-38`: **Good coverage of the new error codes**

Each newly-added enumerator has a matching human-readable message.  👍

</details>
<details>
<summary>components/core/CMakeLists.txt (1)</summary>

`480-483`: **QueryHandlerImpl is only compiled into the unit-test target**

`QueryHandlerImpl.cpp` appears under `SOURCE_FILES_unitTest`, meaning production code that instantiates `QueryHandler` will not be able to link against the implementation.  If the intention is to ship this component (not merely test it), move the `.cpp` into the main library target.  

Also, adding the header (`QueryHandlerImpl.hpp`) to the source list is unnecessary; CMake will track it automatically when included.

```diff
-        src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
-        src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
+        src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
```

</details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp (3)</summary>

`43-50`: **Typo in parameter name reduces readability**

`colum_descriptor` is missing an ‘n’.  The typo propagates to several places in the class.  Renaming improves clarity and avoids future copy-paste errors.

```diff
-        static auto create(clp_s::search::ast::ColumnDescriptor* colum_descriptor)
+        static auto create(clp_s::search::ast::ColumnDescriptor* column_descriptor)
```
[ suggest_nitpick ]

---

`246-248`: **Unused variable masks potential errors**

`next_token_it_result` is computed but never used.  
Besides triggering a warning, ignoring the result swallows a possible `ColumnDescriptorTokenIteratorOutOfBound` error.

```diff
-        auto const next_token_it_result{token_it.next()};
+        // Avoid unused-result and propagate potential error when advancing.
+        OUTCOME_TRY(auto const _unused_next, token_it.next());
```
[ suggest_essential_refactor ]

---

`220-228`: **Member `m_case_sensitive_search` is never read**

The flag is set in the constructor but not referenced anywhere in this file.  
Remove it or wire it into comparison sites (e.g., token vs. key name comparisons) to honour the caller’s intent.

</details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp (4)</summary>

`156-164`: **Use of Yoda condition is consistent with guidelines**

The code correctly follows the coding guideline of preferring `false == expression` rather than `!expression` on line 156.

---

`231-244`: **Ensure efficient AST traversal**

The current DFS implementation uses `std::vector` as a stack, which works well. However, there's a potential optimization in line 236 where you iterate through all operands. If the order doesn't matter, using a reverse iterator could avoid the need to push operands in reverse order for proper DFS traversal.


The implementation is correct as is, but you might want to consider this optimization if the order of traversal isn't important.

---

`271-274`: **Good handling of prefix wildcard cases**

The code correctly handles the case where a prefix wildcard might match nothing by adding an additional iterator.


The comment at line 272 also provides clear documentation of the intent, which is excellent.

---

`315-320`: **Properly marked stub implementation**

This method is properly implemented as a stub that returns a "method not implemented" error, which is appropriate for a work-in-progress PR. The parameters are annotated with `[[maybe_unused]]` which is good practice.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T00:02:02Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🧹 Nitpick comments (9)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/ErrorCode.cpp (2)</summary><blockquote>

`18-47`: **Consider compile-time enforcement that all enumerators are mapped.**

The `switch` covers every enumerator that currently exists, but it will silently fall back to `"Unknown error code enum."` if a future enumerator is added and the author forgets to update this function.  
A small compile-time safeguard can avoid that foot-gun, e.g.

```cpp
static_assert(
    static_cast<int>(ErrorCodeEnum::UnsupportedNamespace) + 1 ==
        ystdlib::enum_size_v<ErrorCodeEnum>,   // or another meta-func you may have
    "ErrorCode::message() is missing cases for some ErrorCodeEnum values"
);
```

(or generate the table with `magic_enum`/`nameof` if available).  
This keeps the mapping exhaustive even as the project evolves.

---

`20-44`: **Return `std::string_view` to avoid per-call heap allocations.**

`message()` currently allocates a fresh `std::string` on every call.  
Because the returned texts are static and immutable, you can safely return
`std::string_view` (or `char const*`) instead:

```diff
-auto ErrorCategory::message(ErrorCodeEnum error_enum) const -> std::string {
+auto ErrorCategory::message(ErrorCodeEnum error_enum) const -> std::string_view {
```

and change each branch to `return "literal";`.  
This removes dynamic allocation and slightly shrinks the call-site code size without sacrificing usability.

</blockquote></details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/utils.hpp (2)</summary><blockquote>

`48-53`: **Pass `filter` by reference (or `gsl::not_null`) instead of raw pointer.**

`evaluate_filter_against_literal_type_value_pair` takes a raw
`FilterExpr const*`.  Within the project this pointer is expected to be non-null,
because a null pointer already maps to `ErrorCodeEnum::QueryExpressionIsNull`
elsewhere.

Prefer an explicit contract by:

```diff
-auto evaluate_filter_against_literal_type_value_pair(
-        clp_s::search::ast::FilterExpr const* filter,
+auto evaluate_filter_against_literal_type_value_pair(
+        clp_s::search::ast::FilterExpr const& filter,
```

(or `not_null<FilterExpr const*>`) to make misuse impossible at compile time
and avoid repeated null checks inside the implementation.

---

`4-12`: **Include guards use relative includes – consider project-wide include paths.**

The header reaches into sibling modules via lengthy relative paths:
`../../../../clp_s/search/ast/…`.  This can become fragile when files move.
If possible, expose these headers via the build system’s include-path and switch to
angle-bracket form:

```cpp
#include <clp_s/search/ast/FilterExpr.hpp>
#include <clp_s/search/ast/Literal.hpp>
```

Doing so improves readability and portability across IDEs.

</blockquote></details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp (1)</summary><blockquote>

`251-251`: **Unused variable `next_token_it_result`**

`auto const next_token_it_result{token_it.next()};` is evaluated but never used, incurring an unnecessary `next()` call (and potential error handling). Remove the statement or use the result.

</blockquote></details>
<details>
<summary>components/core/tests/test-ffi_ir_stream_search_utils.cpp (4)</summary><blockquote>

`16-29`: **Prefer project-wide include paths over brittle relative paths**

The test currently relies on multi-level `../src/...` relative includes.  
If the test directory ever moves (or is re-organised by CMake’s `add_test`), these paths will silently break.  Consider exposing `src` as a public include directory in your build system and switching to angle-bracket includes such as:

```cpp
#include <clp/ffi/encoding_methods.hpp>
#include <clp/ffi/ir_stream/search/utils.hpp>
```

This makes the tests location-agnostic and improves maintainability.

---

`145-162`: **Minor efficiency win in `get_encoded_text_ast`**

1. `dict_vars.reserve(dict_var_bounds.size() / 2);`  
   Reserving avoids repeated reallocations while building `dict_vars`.

2. The inner loop currently constructs substrings via iterator math.  
   `std::string{text.substr(begin_pos, end_pos - begin_pos)}` is clearer and guards against accidental iterator misuse.

Diff sketch:

```diff
-    std::vector<std::string> dict_vars;
+    std::vector<std::string> dict_vars;
+    dict_vars.reserve(dict_var_bounds.size() / 2);
...
-        dict_vars.emplace_back(text.cbegin() + begin_pos, text.cbegin() + end_pos);
+        dict_vars.emplace_back(text.substr(begin_pos, end_pos - begin_pos));
```

---

`499-505`: **Typo: `filer_operand_literal_type` → `filter_operand_literal_type`**

The variable is misspelled here and in the subsequent call.  While harmless, keeping names consistent aids readability and grep-ability.

```diff
-auto const [filter_expr, filer_operand_literal_type]{
+auto const [filter_expr, filter_operand_literal_type]{
 ...
-REQUIRE(check_filter_evaluation(filter_to_test.get(), filer_operand_literal_type));
+REQUIRE(check_filter_evaluation(filter_to_test.get(), filter_operand_literal_type));
```

---

`304-335`: **Constraint duplication can be simplified**

The `requires` clause `std::is_same_v<...>` is repeated on both the forward declaration and the definition of `check_filter_evaluation_against_encoded_text_asts`.  Keeping it only on the declaration (or on a `using concept`) reduces verbosity:

```cpp
template <EncodedVariableT encoded_variable_t>
auto check_filter_evaluation_against_encoded_text_asts(...);
```

where `template<typename T> concept EncodedVariableT = std::same_as<...> || ...;`

This keeps the intent in one place and avoids accidental divergence if the constraint list changes.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 411bb91a1572c10bfee6e3534237fc5dcfe43cd4 and d651b1bc219a1662534bdfeec25887b9cb02ec26.

</details>

<details>
<summary>📒 Files selected for processing (9)</summary>

* `components/core/CMakeLists.txt` (2 hunks)
* `components/core/src/clp/ffi/ir_stream/search/ErrorCode.cpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/ErrorCode.hpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/QueryHandler.hpp` (3 hunks)
* `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/utils.cpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/utils.hpp` (1 hunks)
* `components/core/tests/test-ffi_ir_stream_search_utils.cpp` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* components/core/CMakeLists.txt
* components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (1)</summary>

<details>
<summary>`**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.</summary>

> `**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.

- `components/core/src/clp/ffi/ir_stream/search/ErrorCode.cpp`
- `components/core/src/clp/ffi/ir_stream/search/ErrorCode.hpp`
- `components/core/src/clp/ffi/ir_stream/search/utils.cpp`
- `components/core/src/clp/ffi/ir_stream/search/utils.hpp`
- `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp`
- `components/core/tests/test-ffi_ir_stream_search_utils.cpp`
- `components/core/src/clp/ffi/ir_stream/search/QueryHandler.hpp`

</details>

</details><details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandler.hpp (2)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp (2)</summary>

* `query` (123-128)
* `auto_gen_node_id_value_pairs` (149-152)

</details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp (5)</summary>

* `create` (291-312)
* `create` (291-295)
* `is_auto_generated` (110-111)
* `is_auto_generated` (280-288)
* `is_auto_generated` (280-280)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (11)</summary>

* GitHub Check: centos-stream-9-dynamic-linked-bins
* GitHub Check: ubuntu-jammy-static-linked-bins
* GitHub Check: centos-stream-9-static-linked-bins
* GitHub Check: ubuntu-jammy-lint
* GitHub Check: ubuntu-jammy-dynamic-linked-bins
* GitHub Check: lint-check (ubuntu-latest)
* GitHub Check: build-macos (macos-13, false)
* GitHub Check: build-macos (macos-14, true)
* GitHub Check: build-macos (macos-15, true)
* GitHub Check: build-macos (macos-13, true)
* GitHub Check: build-macos (macos-15, false)

</details>

<details>
<summary>🔇 Additional comments (4)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/ErrorCode.hpp (1)</summary>

`12-25`: **Verify ABI-stability of the new enumerator ordering.**

The new error codes were inserted in the middle of `ErrorCodeEnum` without explicit
integral values.  If any component serialises or persists these values (e.g., across
FFI boundaries or on disk), the implicit renumbering will break compatibility.

1. Confirm that consumers treat the enum purely as symbolic, or  
2. Assign explicit values (and append new codes at the end) to guarantee stability.

```cpp
enum class ErrorCodeEnum : uint8_t {
    AstDynamicCastFailure            = 0,
    ColumnDescriptorTokenIteratorOutOfBound,
    /* … */
    UnsupportedNamespace,
};
```

Please double-check downstream usage to avoid subtle mismatches.

</details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/utils.cpp (1)</summary>

`343-349`: _⚠️ Potential issue_

**Incorrect semantics for `EXISTS` / `NEXISTS` filters**

`EXISTS` and `NEXISTS` currently return constant values that do **not** depend on whether a value is actually present.  
As written, `EXISTS` always succeeds, while `NEXISTS` can never succeed, which breaks typical query semantics.

```diff
-    if (FilterOperation::EXISTS == op) {
-        return true;
-    }
-    if (FilterOperation::NEXISTS == op) {
-        return false;
-    }
+    if (FilterOperation::EXISTS == op) {
+        return value.has_value();
+    }
+    if (FilterOperation::NEXISTS == op) {
+        return false == value.has_value();
+    }
```

Please adjust the logic (and corresponding tests) so that presence/absence is respected.



<details>
<summary>⛔ Skipped due to learnings</summary>

```
Learnt from: LinZhihao-723
PR: y-scope/clp#856
File: components/core/src/clp/ffi/ir_stream/search/utils.cpp:340-347
Timestamp: 2025-04-26T02:19:33.275Z
Learning: In the CLP codebase, the FilterOperation::EXISTS and FilterOperation::NEXISTS operations have specific semantics that differ from simply checking if a value exists. EXISTS always returns true and NEXISTS always returns false in the evaluate_filter_against_literal_type_value_pair function. The optional::has_value() is used specifically to differentiate empty objects from concrete values, not for existence checks in filter operations.
```

</details>

</details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp (1)</summary>

`88-90`: **`match()` ignores descriptor token’s own literal‐type constraints**

`matches_any(schema_tree_node_type_to_literal_types(type))` checks only the *column*-level type mask, not the token-level constraints that may be present (e.g., array/object indices).  
If your design permits token-specific restrictions, consider delegating the check to the token itself or extend the API accordingly.

</details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandler.hpp (1)</summary>

`40-57`: **Factory forwards correctly – no issues found**

The wrapper cleanly forwards arguments to `QueryHandlerImpl::create` using `OUTCOME_TRYX`, preserving error propagation. Nice!

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T18:13:36Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/test_utilis.cpp (3)</summary><blockquote>

`272-282`: **Factor out duplicated “unsupported literal-type” branch**  

The early-exit path that evaluates the filter against an **empty** optional and expects `false` is replicated in every helper (`*_integral_values`, `*_bool_values`, …).  
A small utility such as:

```cpp
auto filter_matches_empty_value(FilterExpr const* f,
                                LiteralType filter_literal_type,
                                LiteralType value_literal_type) -> bool;
```

would eliminate ~25 duplicated lines, improving readability and reducing maintenance overhead.

---

`494-499`: **Typo: `filer_operand_literal_type` → `filter_operand_literal_type`**  

A simple spelling error leaks into the public test output (via `CAPTURE`).  
Renaming improves clarity and avoids confusion for future readers.

```diff
-auto const [filter_expr, filer_operand_literal_type]{
+auto const [filter_expr, filter_operand_literal_type]{
         GENERATE_COPY(from_range(filter_and_type_pairs))
 };
 …
-REQUIRE(check_filter_evaluation(filter_to_test.get(), filer_operand_literal_type));
+REQUIRE(check_filter_evaluation(filter_to_test.get(), filter_operand_literal_type));
```

---

`1-13`: **File name typo (`test_utilis.cpp`)**  

Consider renaming the file to `test_utils.cpp` for consistency with conventional English spelling and to aid discoverability by IDE search tools.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between d651b1bc219a1662534bdfeec25887b9cb02ec26 and 172d8a470ba429cbc24c11d1134506bd722ff36d.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `components/core/CMakeLists.txt` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/test/test_utilis.cpp` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* components/core/CMakeLists.txt

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (1)</summary>

<details>
<summary>`**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.</summary>

> `**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.

- `components/core/src/clp/ffi/ir_stream/search/test/test_utilis.cpp`

</details>

</details><details>
<summary>🪛 Cppcheck (2.10-2)</summary>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/test_utilis.cpp</summary>

[error] 420-420: syntax error

(syntaxError)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (11)</summary>

* GitHub Check: centos-stream-9-dynamic-linked-bins
* GitHub Check: ubuntu-jammy-static-linked-bins
* GitHub Check: ubuntu-jammy-dynamic-linked-bins
* GitHub Check: centos-stream-9-static-linked-bins
* GitHub Check: ubuntu-jammy-lint
* GitHub Check: lint-check (ubuntu-latest)
* GitHub Check: build-macos (macos-14, true)
* GitHub Check: build-macos (macos-15, false)
* GitHub Check: build-macos (macos-15, true)
* GitHub Check: build-macos (macos-14, false)
* GitHub Check: build-macos (macos-13, false)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T18:18:36Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>♻️ Duplicate comments (2)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/utils.cpp (1)</summary><blockquote>

`262-268`: **Add explicit type checking for encoding types.**

The current implementation assumes that any non-`EightByteEncodedTextAst` value is a `FourByteEncodedTextAst`. If a value contains a different type, this will result in undefined behaviour when accessing the variant with an incorrect type.


Replace the ternary operator with explicit checks:

```diff
-    auto const optional_value_operand{
-            value.is<clp::ir::EightByteEncodedTextAst>()
-                    ? value.get_immutable_view<clp::ir::EightByteEncodedTextAst>()
-                              .decode_and_unparse()
-                    : value.get_immutable_view<clp::ir::FourByteEncodedTextAst>()
-                              .decode_and_unparse()
-    };
+    std::optional<std::string> optional_value_operand;
+    if (value.is<clp::ir::EightByteEncodedTextAst>()) {
+        optional_value_operand =
+            value.get_immutable_view<clp::ir::EightByteEncodedTextAst>()
+                 .decode_and_unparse();
+    }
+    else if (value.is<clp::ir::FourByteEncodedTextAst>()) {
+        optional_value_operand =
+            value.get_immutable_view<clp::ir::FourByteEncodedTextAst>()
+                 .decode_and_unparse();
+    }
+    else {
+        // Neither encoded-text AST—return a clear error instead of throwing.
+        return ErrorCode{ErrorCodeEnum::LiteralTypeUnexpected};
+    }
```

</blockquote></details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/test_utilis.cpp (1)</summary><blockquote>

`155-158`: **Add bounds checking when extracting dictionary variables.**

The code doesn't validate that the bounds in `dict_var_bounds` are valid before using them to create substrings. This could lead to undefined behaviour if the bounds are incorrect.


Add explicit bounds checks:

```diff
    for (size_t i{0}; i < dict_var_bounds.size(); i += 2) {
        auto const begin_pos{static_cast<size_t>(dict_var_bounds[i])};
        auto const end_pos{static_cast<size_t>(dict_var_bounds[i + 1])};
+       REQUIRE(begin_pos <= end_pos);      // ensure we don't underflow
+       REQUIRE(end_pos <= text.size());    // ensure we stay within bounds
        dict_vars.emplace_back(text.cbegin() + begin_pos, text.cbegin() + end_pos);
    }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (6)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/test_utilis.cpp (2)</summary><blockquote>

`493-494`: **Fix typo in variable name 'filer_operand_literal_type'.**

The variable name has a typo and should be `filter_operand_literal_type` (missing 't').


```diff
-    auto const [filter_expr, filer_operand_literal_type]{
+    auto const [filter_expr, filter_operand_literal_type]{
            GENERATE_COPY(from_range(filter_and_type_pairs))
    };
```

---

`1-1`: **Filename contains a typo.**

The filename `test_utilis.cpp` appears to have a typo and should be `test_utils.cpp`.

</blockquote></details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp (2)</summary><blockquote>

`6-6`: **The test case name "WIP" is too vague.**

Consider using a more descriptive name that indicates what functionality is being tested, such as "SchemaTreeNodeInsertion" or "QueryHandlerSchemaTreeSetup".

```diff
-TEST_CASE("WIP", "[ffi]") {
+TEST_CASE("SchemaTreeNodeInsertion", "[ffi]") {
```

---

`24-25`: **Unused schema tree.**

Two schema trees are created but only `auto_gen_keys_schema_tree` is used in the test output. Either use `user_gen_keys_schema_tree` or remove it to avoid confusion.

</blockquote></details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/utils.hpp (1)</summary><blockquote>

`40-40`: **Fix grammatical error in comment.**

Correct the grammatical error in the comment.

```diff
-* NOTE: It is assume that all the keys in the schema tree to test don't contain escaped characters.
+* NOTE: It is assumed that all the keys in the schema tree to test don't contain escaped characters.
```

</blockquote></details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/utils.cpp (1)</summary><blockquote>

`27-71`: **Complex traversal algorithm could benefit from more comments.**

The traversal algorithm is complex with several edge cases handled. Consider adding more comments to clarify the logic, particularly regarding the different query extension strategies and the purpose of each branch.

```diff
+    // Use depth-first traversal to build all possible query patterns
     std::vector<std::pair<std::string, SchemaTree::Node::id_t>> search_stack;

     auto push_all_children_to_stack
             = [&](SchemaTree::Node const& node, std::string const& query) -> void {
+        // Add all children of the node to the search stack with the current query context
         for (auto const node_id : node.get_children_ids()) {
             search_stack.emplace_back(query, node_id);
         }
     };

+    // Initialize search with empty and wildcard query patterns from root
     push_all_children_to_stack(schema_tree.get_root(), "");
     push_all_children_to_stack(schema_tree.get_root(), "*");

     while (false == search_stack.empty()) {
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 172d8a470ba429cbc24c11d1134506bd722ff36d and 8ea688d05cdd2cef42db7eff125c14bf671aca2c.

</details>

<details>
<summary>📒 Files selected for processing (8)</summary>

* `components/core/CMakeLists.txt` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/ErrorCode.cpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/test/test_utilis.cpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/test/utils.cpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/test/utils.hpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/utils.cpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/utils.hpp` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (3)</summary>

* components/core/CMakeLists.txt
* components/core/src/clp/ffi/ir_stream/search/ErrorCode.cpp
* components/core/src/clp/ffi/ir_stream/search/utils.hpp

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (1)</summary>

<details>
<summary>`**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.</summary>

> `**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.

- `components/core/src/clp/ffi/ir_stream/search/test/utils.hpp`
- `components/core/src/clp/ffi/ir_stream/search/test/utils.cpp`
- `components/core/src/clp/ffi/ir_stream/search/utils.cpp`
- `components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp`
- `components/core/src/clp/ffi/ir_stream/search/test/test_utilis.cpp`

</details>

</details><details>
<summary>🧠 Learnings (1)</summary>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp (1)</summary>

```
Learnt from: LinZhihao-723
PR: y-scope/clp#557
File: components/core/tests/test-ir_encoding_methods.cpp:1216-1286
Timestamp: 2024-10-13T09:27:43.408Z
Learning: In the unit test case `ffi_ir_stream_serialize_schema_tree_node_id` in `test-ir_encoding_methods.cpp`, suppressing the `readability-function-cognitive-complexity` warning is acceptable due to the expansion of Catch2 macros in C++ tests, and such test cases may not have readability issues.
```

</details>

</details><details>
<summary>🧬 Code Graph Analysis (3)</summary>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/utils.hpp (1)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/utils.cpp (2)</summary>

* `os` (76-84)
* `os` (76-79)

</details>

</blockquote></details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/utils.cpp (1)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/utils.hpp (3)</summary>

* `node_id` (25-28)
* `node_id` (25-25)
* `os` (47-50)

</details>

</blockquote></details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/test_utilis.cpp (1)</summary><blockquote>

<details>
<summary>components/core/src/clp_s/search/ast/ColumnDescriptor.hpp (1)</summary>

* `static_cast` (241-241)

</details>

</blockquote></details>

</details><details>
<summary>🪛 Cppcheck (2.10-2)</summary>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/test_utilis.cpp</summary>

[error] 419-419: syntax error

(syntaxError)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (9)</summary>

* GitHub Check: ubuntu-jammy-lint
* GitHub Check: centos-stream-9-static-linked-bins
* GitHub Check: centos-stream-9-dynamic-linked-bins
* GitHub Check: ubuntu-jammy-dynamic-linked-bins
* GitHub Check: ubuntu-jammy-static-linked-bins
* GitHub Check: lint-check (ubuntu-latest)
* GitHub Check: build-macos (macos-13, true)
* GitHub Check: build-macos (macos-14, false)
* GitHub Check: build-macos (macos-13, false)

</details>

<details>
<summary>🔇 Additional comments (8)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/utils.cpp (3)</summary>

`169-170`: **Well-structured code using std::ranges::all_of.**

Good use of modern C++ ranges to verify all filter evaluations. This approach is cleaner and more maintainable than traditional loops.

---

`282-301`: **Good mapping of schema node types to literal types.**

The function provides a clear mapping while handling all possible cases. The comments about future support for `LiteralType::ObjectT` are valuable for tracking potential enhancements.

---

`351-383`: **Comprehensive switch statement for evaluating different literal types.**

The function properly handles all literal types and returns appropriate error codes for unsupported or unexpected types. The error handling via `outcome_v2::std_result` is a good choice for propagating failures.

</details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/test_utilis.cpp (2)</summary>

`298-329`: **Well-implemented templated function for testing different encoded text AST types.**

This function cleanly handles both four-byte and eight-byte encoded variables with the same code, reducing duplication and improving maintainability.

---

`419-499`: **Comprehensive test case covering all filter operations and literal types.**

The test generates combinations of filter operations and literal types, providing thorough test coverage for the filter evaluation logic. The use of `GENERATE_COPY` with `from_range` is an effective way to parameterize tests in Catch2.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Cppcheck (2.10-2)</summary>

[error] 419-419: syntax error

(syntaxError)

</details>

</details>

</details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/utils.hpp (1)</summary>

`14-51`: **The PossibleMatches class is well-structured.**

The class provides a clean interface with appropriate accessors and modifiers for tracking schema tree node matches. The use of bitmasking for types and sets for IDs is an efficient approach.

</details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/utils.cpp (2)</summary>

`37-37`: **Coding guideline followed correctly.**

Excellent use of `false == search_stack.empty()` rather than `!search_stack.empty()` as per the coding guidelines.

---

`76-84`: **Stream operator implementation is clean and effective.**

The output stream operator provides a clear, readable format for the query-to-possible-matches map, using the PossibleMatches::serialize method effectively.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T20:53:49Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 11**

<details>
<summary>🔭 Outside diff range comments (1)</summary><blockquote>

<details>
<summary>components/core/expected.old (1)</summary><blockquote>

`1-72`: _💡 Verification agent_

<details>
<summary>❓ Verification inconclusive</summary>

**Clarify the purpose of the generated `expected.old` file**

This new artefact contains pattern markers rather than executable code.  It is not referenced from CMake or any test target, so future contributors may be unsure whether it is:

* A golden-file for unit tests,
* Deprecated debug output,
* Or an accidental commit.

Please either:
1. Add a short header comment explaining its role, **or**
2. Move it into an obviously test-data directory (e.g. `tests/resources/`) and reference it from the relevant test, **or**
3. Remove it if it was committed unintentionally.

This prevents dead files from accumulating in the repository.

---

🏁 Script executed:

```shell
#!/bin/bash
# Search for any references to the expected.old artifact in the codebase
rg -n "expected\.old"
```

Length of output: 23

---



</details>

**Clarify purpose of `components/core/expected.old`**

The new `components/core/expected.old` file contains pattern markers rather than executable code and isn’t referenced by any CMakeLists.txt or test target. Future contributors may be unsure whether it’s:

- A golden‐file for unit tests  
- Deprecated debug output  
- An accidental commit  

Please address in one of the following ways:

- Add a short header comment at the top of `components/core/expected.old` explaining its purpose.  
- Move it into an obvious test‐data directory (e.g. `tests/resources/`) and update the relevant test(s) to reference it.  
- Remove it if it was committed unintentionally.

This will prevent dead files from accumulating in the repository.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 8ea688d05cdd2cef42db7eff125c14bf671aca2c and b8d69b514d6471cec0a7daf9a9d723851ef7dbab.

</details>

<details>
<summary>📒 Files selected for processing (7)</summary>

* `components/core/CMakeLists.txt` (1 hunks)
* `components/core/expected.old` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/test/utils.cpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/test/utils.hpp` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp (4)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/utils.hpp (6)</summary>

* `nodiscard` (25-27)
* `nodiscard` (29-31)
* `get_schema_tree_column_queries` (75-76)
* `node_id` (43-46)
* `node_id` (43-43)
* `trivial_new_projected_schema_tree_node_callback` (62-66)

</details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp (5)</summary>

* `is_auto_generated` (110-111)
* `is_auto_generated` (282-290)
* `is_auto_generated` (282-282)
* `create` (293-314)
* `create` (293-297)

</details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/utils.cpp (4)</summary>

* `get_schema_tree_column_queries` (83-150)
* `get_schema_tree_column_queries` (83-84)
* `trivial_new_projected_schema_tree_node_callback` (75-81)
* `trivial_new_projected_schema_tree_node_callback` (75-79)

</details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandler.hpp (1)</summary>

* `new_projected_schema_tree_node_callback` (40-46)

</details>

</blockquote></details>

</details><details>
<summary>🪛 Cppcheck (2.10-2)</summary>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp</summary>

[style] 318-318: The function 'evaluate_node_id_value_pairs' is never used.

(unusedFunction)

</details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/utils.cpp</summary>

[style] 56-56: The function 'get_projectable_node_ids' is never used.

(unusedFunction)

---

[style] 75-75: The function 'trivial_new_projected_schema_tree_node_callback' is never used.

(unusedFunction)

---

[style] 83-83: The function 'get_schema_tree_column_queries' is never used.

(unusedFunction)

</details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp</summary>

[error] 214-214: syntax error

(syntaxError)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (2)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp (2)</summary>

`270-276`: _🛠️ Refactor suggestion_

**Guard call to `next()` to avoid undefined behaviour**  
`begin_token_it.next()` is invoked after confirming `!is_last()`, but for wildcard prefixes it’s possible that `next()` still fails (e.g., iterator creation error). Consider propagating the outcome:

```diff
-    it->second.emplace_back(OUTCOME_TRYX(begin_token_it.next()));
+    auto next_it_res = begin_token_it.next();
+    OUTCOME_TRY(next_it, next_it_res);
+    it->second.emplace_back(std::move(next_it));
```

This keeps error handling symmetrical with other iterator operations.  




> Likely an incorrect or invalid review comment.

---

`113-140`: <details>
<summary>❓ Verification inconclusive</summary>

**Avoid assignment-in-`if` for clearer intent & capture original exception**  
The repeated pattern

```cpp
if (query = Pass{}.run(query); ...)
```

mixes assignment with the condition, making the control flow harder to read and easy to mis-interpret as a bug.  
Additionally, the `catch` block discards diagnostic information.

```diff
-try {
-    if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
+try {
+    query = clp_s::search::ast::OrOfAndForm{}.run(query);
+    if (
         nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
...
-} catch (std::exception const& ex) {
-    return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+} catch (std::exception const& ex) {
+    return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed}
+           .with_message(ex.what());   // assuming ErrorCode supports this
 }
```

Benefits:  
• Intent is explicit;  
• Debugging is easier when the original exception message is retained.

---

🏁 Script executed:

```shell
#!/bin/bash
# Search for the definition of ErrorCode and whether it supports with_message
rg -nE "^(class|struct)\s+ErrorCode" .
rg -n "with_message" .
```

Length of output: 128

---



</details>

**Avoid assignment-in-if and preserve exception details**  
The in-if assignments obscure control flow and the catch block currently drops the original exception message, making failures harder to diagnose.

Please update in components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp within `preprocess_query`:

• Refactor each pass so the result is assigned before the `if`, e.g.:

```diff
-    if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
-        nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
-    {
-        return query;
-    }
+    query = clp_s::search::ast::OrOfAndForm{}.run(query);
+    if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(query)) {
+        return query;
+    }
```

Apply the same pattern for `NarrowTypes`.  

• In the `catch` block, propagate or log the exception message:

```diff
-} catch (std::exception const& ex) {
-    return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
-}
+} catch (std::exception const& ex) {
+    // If ErrorCode supports attaching a message:
+    return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed}
+           .with_message(ex.what());
+    // Otherwise, log ex.what() or extend ErrorCode to retain diagnostic info.
+}
```

This makes intent explicit and ensures diagnostic context is not lost.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T20:49:18Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>components/core/CMakeLists.txt (1)</summary><blockquote>

`460-462`: **Approve QueryHandlerImpl additions and consider removing headers**

The new `QueryHandlerImpl.cpp` and `QueryHandlerImpl.hpp`, along with its test (`test_QueryHandlerImpl.cpp`), are correctly included in the `unitTest` source list. This ensures the implementation and its tests are compiled together. To reduce clutter and prevent stale entries, consider dropping the header file (`.hpp`) from `SOURCE_FILES_unitTest`, since headers are resolved via includes.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between b8d69b514d6471cec0a7daf9a9d723851ef7dbab and 350552c270933be411448dc88c0e2c18c4661fab.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `components/core/CMakeLists.txt` (1 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (9)</summary>

* GitHub Check: ubuntu-jammy-lint
* GitHub Check: ubuntu-jammy-static-linked-bins
* GitHub Check: ubuntu-jammy-dynamic-linked-bins
* GitHub Check: centos-stream-9-dynamic-linked-bins
* GitHub Check: centos-stream-9-static-linked-bins
* GitHub Check: build-macos (macos-14, false)
* GitHub Check: build-macos (macos-15, false)
* GitHub Check: build-macos (macos-14, true)
* GitHub Check: build-macos (macos-13, false)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T20:51:51Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T21:54:41Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T21:55:26Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T21:56:01Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T21:56:10Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T22:03:40Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T22:04:03Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T22:11:34Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T22:11:57Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T22:13:31Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T22:13:59Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T22:15:11Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T22:15:38Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T22:23:34Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T22:23:56Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T22:24:12Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T22:24:34Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T22:25:23Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T22:25:44Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T22:26:18Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T22:26:43Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T22:27:25Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T22:28:01Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T22:39:27Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T22:39:37Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T22:40:07Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T22:40:08Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T22:41:53Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T22:42:18Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between a5fedf5a9ceff91e433ddee50a63ba2d25bdd441 and 141242c5ecf7b7e2533e647ba7282177b21c15ca.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/test/utils.cpp` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (1)</summary>

<details>
<summary>`**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.</summary>

> `**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.

- `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp`
- `components/core/src/clp/ffi/ir_stream/search/test/utils.cpp`
- `components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp`

</details>

</details><details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/utils.cpp (1)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/utils.hpp (5)</summary>

* `type` (33-35)
* `type` (37-39)
* `node_id` (43-46)
* `node_id` (43-43)
* `os` (78-81)

</details>

</blockquote></details>

</details><details>
<summary>🪛 Cppcheck (2.10-2)</summary>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/utils.cpp</summary>

[style] 58-58: The function 'get_projectable_node_ids' is never used.

(unusedFunction)

---

[style] 78-78: The function 'trivial_new_projected_schema_tree_node_callback' is never used.

(unusedFunction)

---

[style] 86-86: The function 'get_schema_tree_column_queries' is never used.

(unusedFunction)

</details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp</summary>

[error] 221-221: syntax error

(syntaxError)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (11)</summary>

* GitHub Check: centos-stream-9-dynamic-linked-bins
* GitHub Check: centos-stream-9-static-linked-bins
* GitHub Check: ubuntu-jammy-dynamic-linked-bins
* GitHub Check: ubuntu-jammy-lint
* GitHub Check: ubuntu-jammy-static-linked-bins
* GitHub Check: build-macos (macos-14, false)
* GitHub Check: build-macos (macos-14, true)
* GitHub Check: lint-check (ubuntu-latest)
* GitHub Check: build-macos (macos-15, false)
* GitHub Check: build-macos (macos-13, true)
* GitHub Check: build-macos (macos-13, false)

</details>

<details>
<summary>🔇 Additional comments (12)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/utils.cpp (4)</summary>

`58-67`: **Correctly implemented non-object node filtering.**

The implementation properly filters out object nodes and returns only non-object nodes, which is essential for projection operations. This ensures only leaf values are projected, not container nodes.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Cppcheck (2.10-2)</summary>

[style] 58-58: The function 'get_projectable_node_ids' is never used.

(unusedFunction)

</details>

</details>

---

`69-76`: **Format string for serialization is correctly implemented.**

The format string with balanced double braces properly escapes curly braces for `fmt::format` while maintaining the placeholder for the joined node IDs.

---

`86-153`: **Comprehensive column query generation with DFS approach.**

The implementation uses a depth-first search to generate all possible column query patterns for a schema tree, including patterns with wildcards. This thorough approach generates five valid query patterns for each node as documented in the comments, ensuring complete coverage for testing.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Cppcheck (2.10-2)</summary>

[style] 86-86: The function 'get_schema_tree_column_queries' is never used.

(unusedFunction)

</details>

</details>

---

`111-111`: **Following coding guidelines for boolean expressions.**

The expression correctly uses `false == search_stack.empty()` rather than `!search_stack.empty()`, adhering to the project's coding guidelines.

</details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp (5)</summary>

`89-91`: **Correct implementation of node type matching.**

The `match` method properly converts schema tree node types to compatible literal types before checking compatibility with the column descriptor, ensuring correct column resolution.

---

`148-149`: **TODO comment needs to be completed.**

The comment indicates an incomplete documentation of error codes that can be returned from the `evaluate_node_id_value_pairs` method.

Please complete the documentation for possible error codes to ensure API clarity. This is important for users of this method to understand all possible failure modes.

---

`237-302`: **Safe implementation avoiding iterator invalidation.**

The implementation safely avoids iterator invalidation by:
1. First collecting new partial resolutions in a separate container
2. Only after completing iteration, inserting the new items into the map

This prevents the issue where insertions might trigger rehashing and invalidate iterators being used in the loop.

---

`259-261`: **Deferred implementation of object search.**

The code explicitly notes that object search functionality is not yet implemented and will be addressed in future work, providing a clear path for future development.

---

`321-329`: **Comprehensive leaf node resolution logic.**

The method thoroughly checks both matching conditions:
1. Token type compatibility with node type
2. Either wildcard matching or exact key name matching

This careful handling ensures accurate column resolution.

</details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp (3)</summary>

`221-352`: **Comprehensive test for partial column resolution.**

This test thoroughly validates the `update_partially_resolved_columns` method by:
1. Creating a complex schema tree with various node types and relationships
2. Generating column queries and expected resolutions
3. Creating a query with matchable and unmatchable expressions
4. Updating resolutions for all nodes in both namespaces
5. Verifying actual resolutions match expected ones

This provides strong confidence in the correctness of the column resolution logic.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Cppcheck (2.10-2)</summary>

[error] 221-221: syntax error

(syntaxError)

</details>

</details>

---

`186-188`: **Correctly checks for empty projectable node IDs.**

The condition properly checks if `projectable_node_ids` is empty before continuing, ensuring only columns with projectable leaf nodes are included in projections.

---

`407-439`: **Well-designed projection callback verification.**

The test uses a callback to capture projected nodes and verify their correctness. The callback implementation:
1. Validates the namespace parameter matches expectations
2. Records projected node IDs by column
3. Verifies uniqueness of projections
4. Returns success for projection handling

This approach thoroughly tests the projection functionality.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T22:48:20Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp (1)</summary><blockquote>

`320-327`: **Stub method still unimplemented**  
Same as noted in the previous review: `evaluate_node_id_value_pairs` continues to return `MethodNotImplemented`. Ensure callers are guarded or postpone merging until the logic lands.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Cppcheck (2.10-2)</summary>

[style] 322-322: The function 'evaluate_node_id_value_pairs' is never used.

(unusedFunction)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 141242c5ecf7b7e2533e647ba7282177b21c15ca and 27560bdd1c18b788682b0b4727b40292e4c9fd7d.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (1)</summary>

<details>
<summary>`**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.</summary>

> `**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.

- `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp`

</details>

</details><details>
<summary>🧠 Learnings (1)</summary>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp (1)</summary>

```
Learnt from: LinZhihao-723
PR: y-scope/clp#873
File: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp:148-157
Timestamp: 2025-05-02T22:27:59.339Z
Learning: In the `QueryHandlerImpl.cpp` file, the `unique_projected_columns` set (using `std::string_view`) is intentionally designed to only check for duplications within the local scope of the `create_projected_columns_and_projection_map` function. The team decided this is an acceptable use of `std::string_view` in a container since the referenced strings remain valid throughout the function's execution.
```

</details>

</details><details>
<summary>🪛 Cppcheck (2.10-2)</summary>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp</summary>

[style] 322-322: The function 'evaluate_node_id_value_pairs' is never used.

(unusedFunction)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (2)</summary>

* GitHub Check: build-macos (macos-13, false)
* GitHub Check: build-macos (macos-13, true)

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp (1)</summary>

`269-276`: _🧹 Nitpick (assertive)_

**Avoid duplicate `is_last()` call & clarify intent of wildcard handling**

`begin_token_it.is_last()` is evaluated twice.  Caching the result improves efficiency and self-documentation:

```diff
-if (false == begin_token_it.is_last() && begin_token_it.is_wildcard()) {
+bool const at_last_token{begin_token_it.is_last()};
+if (false == at_last_token && begin_token_it.is_wildcard()) {
```

Additionally, a brief comment explaining *why* a second iterator is pushed (prefix wildcard may match empty string) would aid future maintainers.  




> Likely an incorrect or invalid review comment.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T23:05:02Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp (1)</summary><blockquote>

`320-327`: **`evaluate_node_id_value_pairs` still unimplemented – keep documentation in sync**  
This stub returns `MethodNotImplemented`, which is fine for a WIP, but ensure
the header comment explicitly states that the method is not yet implemented so
callers do not treat a failed `outcome` as an exceptional state.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Cppcheck (2.10-2)</summary>

[style] 322-322: The function 'evaluate_node_id_value_pairs' is never used.

(unusedFunction)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 141242c5ecf7b7e2533e647ba7282177b21c15ca and 27560bdd1c18b788682b0b4727b40292e4c9fd7d.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (1)</summary>

<details>
<summary>`**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.</summary>

> `**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.

- `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp`

</details>

</details><details>
<summary>🧠 Learnings (1)</summary>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp (1)</summary>

```
Learnt from: LinZhihao-723
PR: y-scope/clp#873
File: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp:148-157
Timestamp: 2025-05-02T22:27:59.339Z
Learning: In the `QueryHandlerImpl.cpp` file, the `unique_projected_columns` set (using `std::string_view`) is intentionally designed to only check for duplications within the local scope of the `create_projected_columns_and_projection_map` function. The team decided this is an acceptable use of `std::string_view` in a container since the referenced strings remain valid throughout the function's execution.
```

</details>

</details><details>
<summary>🪛 Cppcheck (2.10-2)</summary>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp</summary>

[style] 322-322: The function 'evaluate_node_id_value_pairs' is never used.

(unusedFunction)

</details>

</details>

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T23:06:08Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T23:48:02Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T23:49:32Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T23:51:54Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T23:52:25Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T23:56:14Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T23:56:40Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T23:56:41Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T23:57:03Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T23:57:30Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T23:57:59Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 27560bdd1c18b788682b0b4727b40292e4c9fd7d and 6adb74e1ef0889aeec00b2f9433acdc0edc91f9e.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `components/core/src/clp/ffi/ir_stream/search/ErrorCode.hpp` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (1)</summary>

<details>
<summary>`**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.</summary>

> `**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.

- `components/core/src/clp/ffi/ir_stream/search/ErrorCode.hpp`

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (11)</summary>

* GitHub Check: centos-stream-9-static-linked-bins
* GitHub Check: centos-stream-9-dynamic-linked-bins
* GitHub Check: ubuntu-jammy-static-linked-bins
* GitHub Check: ubuntu-jammy-dynamic-linked-bins
* GitHub Check: ubuntu-jammy-lint
* GitHub Check: build-macos (macos-14, false)
* GitHub Check: build-macos (macos-13, true)
* GitHub Check: build-macos (macos-15, true)
* GitHub Check: build-macos (macos-14, true)
* GitHub Check: lint-check (ubuntu-latest)
* GitHub Check: lint-check (macos-latest)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-03T00:03:17Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-03T16:51:25Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-03T16:51:48Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>🔭 Outside diff range comments (1)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/ErrorCode.hpp (1)</summary><blockquote>

`40-43`: _🧹 Nitpick (assertive)_

**Enum name in comment differs from actual identifier.**

The docstring lists `ColumnDescriptorTokenIteratorOutOfBound` (singular), but the enum is `…OutOfBounds` (plural).  Keeping comments in sync with code avoids confusion during grep-based searches.

```diff
- * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the column doesn't have any
+ * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds if the column doesn't have any
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 6adb74e1ef0889aeec00b2f9433acdc0edc91f9e and b51e93eb40e8c9c4ba5eaf642fe42f7148474949.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `components/core/src/clp/ffi/ir_stream/search/ErrorCode.cpp` (2 hunks)
* `components/core/src/clp/ffi/ir_stream/search/ErrorCode.hpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (1)</summary>

<details>
<summary>`**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.</summary>

> `**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.

- `components/core/src/clp/ffi/ir_stream/search/ErrorCode.cpp`
- `components/core/src/clp/ffi/ir_stream/search/ErrorCode.hpp`
- `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp`

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (11)</summary>

* GitHub Check: ubuntu-jammy-static-linked-bins
* GitHub Check: ubuntu-jammy-lint
* GitHub Check: centos-stream-9-dynamic-linked-bins
* GitHub Check: ubuntu-jammy-dynamic-linked-bins
* GitHub Check: centos-stream-9-static-linked-bins
* GitHub Check: lint-check (ubuntu-latest)
* GitHub Check: build-macos (macos-14, true)
* GitHub Check: build-macos (macos-14, false)
* GitHub Check: build-macos (macos-15, false)
* GitHub Check: build-macos (macos-13, true)
* GitHub Check: build-macos (macos-13, false)

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/ErrorCode.cpp (1)</summary>

`20-43`: **Expanded error messages read clearly – nice work.**

The new cases cover all added enum values and the wording is concise and user-friendly. No issues spotted.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-03T16:56:04Z
user: login: gibber9809
body: Left an initial set of comments. Read through most of the implementation except for `QueryHandlerImpl.cpp` which I've only skimmed so far -- `QueryHandlerImpl.cpp` mostly seems fine besides the one comment I left about the unsupported namespace error.

Will come back and finish reviewing that part of the code + read through the tests tomorrow.
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T01:33:08Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T01:37:07Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T01:37:33Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T01:41:36Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T01:43:17Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T01:44:31Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T01:47:55Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T01:51:41Z
user: login: gibber9809
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T16:35:57Z
user: login: kirkrodrigues
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T17:42:17Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T18:42:39Z
user: login: kirkrodrigues
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T18:49:38Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T19:21:53Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T19:28:49Z
user: login: kirkrodrigues
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T20:11:06Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>🔭 Outside diff range comments (1)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/ErrorCode.cpp (1)</summary><blockquote>

`20-44`: _⚠️ Potential issue_

**Missing `UnsupportedNamespace` branch – future callers will receive a generic “unknown” message**

`ErrorCodeEnum` advertises an `UnsupportedNamespace` value (see header and recent discussions), yet the `switch` here omits a corresponding case.  Any attempt to surface that error will fall through to the default branch, giving an unhelpful *"Unknown error code enum."* string.

```diff
+        case ErrorCodeEnum::UnsupportedNamespace:
+            return "Encountered a column namespace that is not currently supported.";
```

Adding the branch now avoids silent degradation and keeps diagnostics consistent across the code-base.

</blockquote></details>

</blockquote></details>

<details>
<summary>♻️ Duplicate comments (5)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/utils.hpp (1)</summary><blockquote>

`15-17`: **Deep relative include path – same concern as in previous reviews**  

The five-level `../../../../../` include to reach `clp_s/search/ast/Literal.hpp` is brittle and hampers portability.  Prior reviews agreed this will be addressed via CMake include-paths in a later sweep, so this comment merely records that the issue persists.

</blockquote></details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp (2)</summary><blockquote>

`293-299`: **Variable shadowing of `node_id` obscures intent**

The structured binding re-uses the name `node_id`, shadowing the function parameter and making stack traces/debugging harder.

```diff
-for (auto const [node_id, token_it] : new_partial_resolutions) {
+for (auto const [child_node_id, token_it] : new_partial_resolutions) {
     auto [it, inserted] = partial_resolutions_to_update.try_emplace(
-            node_id,
+            child_node_id,
             std::vector<ColumnDescriptorTokenIterator>{}
     );
-    it->second.emplace_back(token_it);
+    it->second.emplace_back(token_it);
 }
```

---

`271-275`: _⚠️ Potential issue_

**String comparison still ignores the `m_case_sensitive_search` flag**  

A previous review noted that case-insensitive mode is silently bypassed here.  The code remains unchanged, so column descriptors containing mixed-case keys will never resolve when `case_sensitive_match == false`.

```diff
-            if (token_it.get_token() != node_locator.get_key_name()) {
+            bool tokens_equal = m_case_sensitive_search
+                                ? token_it.get_token() == node_locator.get_key_name()
+                                : utils::iequals(token_it.get_token(), node_locator.get_key_name());
+            if (false == tokens_equal) {
                 continue;
             }
```

Please apply the same fix to the analogous check in `handle_column_resolution_on_leaf_node`.

</blockquote></details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp (2)</summary><blockquote>

`145-147`: **Reserve container capacity to avoid repeated re-hashing**  

`unique_projected_columns`, `projected_columns`, and `projected_column_to_original_key`
can reserve `projections.size()` up-front – eliminating unnecessary allocations when
many projection keys are supplied.

---

`120-133`: **Chained assignment inside `if` hampers single-step debugging**

Separating the transformation from the emptiness check clarifies intent and lets
a debugger inspect the intermediate AST.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between b51e93eb40e8c9c4ba5eaf642fe42f7148474949 and 887aae8ea396e180712d56b95d206cb1030755ea.

</details>

<details>
<summary>📒 Files selected for processing (6)</summary>

* `components/core/src/clp/ffi/ir_stream/search/ErrorCode.cpp` (2 hunks)
* `components/core/src/clp/ffi/ir_stream/search/ErrorCode.hpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/test/utils.hpp` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (1)</summary>

<details>
<summary>`**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.</summary>

> `**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.

- `components/core/src/clp/ffi/ir_stream/search/ErrorCode.cpp`
- `components/core/src/clp/ffi/ir_stream/search/ErrorCode.hpp`
- `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp`
- `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp`
- `components/core/src/clp/ffi/ir_stream/search/test/utils.hpp`
- `components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp`

</details>

</details><details>
<summary>🧠 Learnings (2)</summary>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp (1)</summary>

```
Learnt from: LinZhihao-723
PR: y-scope/clp#873
File: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp:148-157
Timestamp: 2025-05-02T22:27:59.347Z
Learning: In the `QueryHandlerImpl.cpp` file, the `unique_projected_columns` set (using `std::string_view`) is intentionally designed to only check for duplications within the local scope of the `create_projected_columns_and_projection_map` function. The team decided this is an acceptable use of `std::string_view` in a container since the referenced strings remain valid throughout the function's execution.
```

</details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/utils.hpp (1)</summary>

```
Learnt from: LinZhihao-723
PR: y-scope/clp#873
File: components/core/src/clp/ffi/ir_stream/search/test/utils.hpp:15-18
Timestamp: 2025-05-02T22:11:55.711Z
Learning: The team plans to systematically improve include paths by adding `${PROJECT_SOURCE_DIR}/src` to CMake targets and using direct include paths instead of deep relative ones in a future PR.
```

</details>

</details><details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/utils.hpp (3)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp (1)</summary>

* `nodiscard` (73-160)

</details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp (3)</summary>

* `is_auto_generated` (108-108)
* `is_auto_generated` (287-295)
* `is_auto_generated` (287-287)

</details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/utils.cpp (2)</summary>

* `os` (155-163)
* `os` (155-158)

</details>

</blockquote></details>

</details><details>
<summary>🪛 Cppcheck (2.10-2)</summary>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp</summary>

[style] 327-327: The function 'evaluate_node_id_value_pairs' is never used.

(unusedFunction)

</details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp</summary>

[error] 206-206: syntax error

(syntaxError)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (11)</summary>

* GitHub Check: centos-stream-9-static-linked-bins
* GitHub Check: ubuntu-jammy-lint
* GitHub Check: centos-stream-9-dynamic-linked-bins
* GitHub Check: ubuntu-jammy-static-linked-bins
* GitHub Check: ubuntu-jammy-dynamic-linked-bins
* GitHub Check: build-macos (macos-13, true)
* GitHub Check: build-macos (macos-15, true)
* GitHub Check: build-macos (macos-14, true)
* GitHub Check: build-macos (macos-13, false)
* GitHub Check: lint-check (ubuntu-latest)
* GitHub Check: lint-check (macos-latest)

</details>

<details>
<summary>🔇 Additional comments (4)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp (1)</summary>

`178-180`: <details>
<summary>❌ Incorrect review comment</summary>

**Potential dangling `string_view` in `ProjectionMap` – verify lifetime**

`projected_column_to_original_key.emplace(column_descriptor.get(), key);`

If `ProjectionMap` stores the second element as `std::string_view`, the view will refer to `key` inside the caller-supplied `projections` vector. That vector is destroyed when `QueryHandlerImpl::create` returns, leaving every view in the map dangling and leading to UB on the first access.

```diff
-            projected_column_to_original_key.emplace(column_descriptor.get(), key);
+            // Copy the string to ensure it out-lives `projections`
+            projected_column_to_original_key.emplace(
+                    column_descriptor.get(),
+                    std::string{key}
+            );
```

Please double-check the typedef of `ProjectionMap` in `QueryHandlerImpl.hpp`.  
If it indeed uses `std::string_view`, adopt the defensive copy above or hold an owning `std::string`.

---

🏁 Script executed:

```shell
#!/bin/bash
# Inspect the value type of `ProjectionMap`
rg -n --context 2 "using .*ProjectionMap" $(fd QueryHandlerImpl.hpp -t f)
```

Length of output: 307

---



</details>

**ProjectionMap stores std::string – no dangling‐view risk**

The typedef in QueryHandlerImpl.hpp (line 108):

```cpp
using ProjectionMap = std::unordered_map<clp_s::search::ast::ColumnDescriptor*, std::string>;
```

means that emplacing `key` into the map creates its own `std::string` copy. There is no `std::string_view` involved, so no dangling reference can occur. No changes are needed; please disregard the earlier suggestion. 

> Likely an incorrect or invalid review comment.

</details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/ErrorCode.hpp (3)</summary>

`13-16`: **Naming convention looks good**

The newly added error codes follow the consistent camel case convention. I noticed that the grammar suggestion for "OutOfBounds" (plural) has been incorporated, which is good.

---

`21-23`: **Error codes are properly structured**

The additional error codes are well organized and provide clear, descriptive names that indicate their purpose within the search functionality.

---

`12-24`: <details>
<summary>❌ Incorrect review comment</summary>

**Note: Discrepancy with AI summary regarding UnsupportedNamespace**

The AI-generated summary mentioned an `UnsupportedNamespace` error code, but it doesn't appear in this implementation. If this error code is needed for the QueryHandlerImpl as described in the summary, it should be added to the enum.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if UnsupportedNamespace is referenced in the codebase but missing from the enum
grep -r "UnsupportedNamespace" --include="*.{cpp,hpp}" components/core/src/clp/ffi/ir_stream/search/
```

Length of output: 100

---

To ensure we haven’t missed any references elsewhere in the repo, let’s search the entire codebase for `UnsupportedNamespace`.

---

🏁 Script executed:

```shell
#!/bin/bash
grep -R "UnsupportedNamespace" -n .
```

Length of output: 35

---



</details>

**Ignore the `UnsupportedNamespace` suggestion**

The identifier `UnsupportedNamespace` isn’t referenced anywhere in the repository, so there’s no need to add it to `ErrorCodeEnum`. The AI summary was incorrect in this case. 

> Likely an incorrect or invalid review comment.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T20:24:18Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T20:25:54Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 3**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp (1)</summary><blockquote>

`296-299`: _⚠️ Potential issue_

**Add a sanity check that KQL parsing succeeded**

There's no check after parsing the KQL expression to verify that parsing succeeded. If parsing fails, `query` could be nullptr, leading to undefined behavior when creating the QueryHandlerImpl.


```diff
 auto query_stream{std::istringstream{kql_query_str}};
 auto query{clp_s::search::kql::parse_kql_expression(query_stream)};
+REQUIRE(query != nullptr);  // Ensure parsing did not fail
 
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 887aae8ea396e180712d56b95d206cb1030755ea and cdead62c99332f0cba054d4c8240d7b7fb5b43a5.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (1)</summary>

<details>
<summary>`**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.</summary>

> `**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.

- `components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp`

</details>

</details><details>
<summary>🪛 Cppcheck (2.10-2)</summary>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp</summary>

[error] 209-209: syntax error

(syntaxError)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (10)</summary>

* GitHub Check: centos-stream-9-static-linked-bins
* GitHub Check: ubuntu-jammy-dynamic-linked-bins
* GitHub Check: centos-stream-9-dynamic-linked-bins
* GitHub Check: ubuntu-jammy-static-linked-bins
* GitHub Check: ubuntu-jammy-lint
* GitHub Check: build-macos (macos-13, true)
* GitHub Check: build-macos (macos-14, true)
* GitHub Check: build-macos (macos-14, false)
* GitHub Check: build-macos (macos-15, false)
* GitHub Check: build-macos (macos-13, false)

</details>

<details>
<summary>🔇 Additional comments (5)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp (5)</summary>

`163-165`: **Good error handling architecture**

The code appropriately erases single wildcard entries from expected resolutions with a clear comment explaining why, showing thoughtful handling of edge cases.

---

`407-408`: **Good test design using EmptyExpr**

The test correctly uses an empty query expression to isolate and test the projection functionality independently.

---

`89-90`: **Correctly follows coding style guidelines**

The code properly follows the coding guideline to prefer `false == <expression>` rather than `!<expression>`.



Also applies to: 107-108, 125-126, 143-144

---

`46-75`: **Well-documented helper functions**

The helper functions have clear and detailed documentation describing their purpose, parameters, and return values, which enhances code readability and maintainability.

---

`302-303`: **Good handling of external library warnings**

The code correctly uses NOLINT directives with clear explanations to silence specific warnings from external libraries, referencing the relevant GitHub issues.



Also applies to: 418-420

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T20:53:48Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>♻️ Duplicate comments (3)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp (3)</summary><blockquote>

`120-131`: **Flatten assignment-in-condition for easier reading & debugging**  

Re-using the assignment expression inside the `if` statement still obscures the intermediate value and complicates single-step debugging.  Consider storing the transformed query in a temporary first, then checking for `EmptyExpr`.  
(This is the same readability issue we discussed in an earlier review.)

---

`145-148`: **Pre-size containers to avoid repeated reallocations**  

`projections.size()` is known up-front; calling `reserve()` on the set, vector, and map prevents extra allocations when many projection keys are supplied.  
This optimisation was suggested previously—re-raising here for completeness.

---

`324-330`: **Stub method may surprise callers – mark clearly or guard at compile-time**  

`evaluate_node_id_value_pairs` always returns `MethodNotImplemented`, yet nothing in the declaration warns users.  A compile-time guard (`static_assert(false, "…")`) or `[[deprecated("Not implemented")]]` attribute would ensure it isn't called accidentally until the real logic lands.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Cppcheck (2.10-2)</summary>

[style] 326-326: The function 'evaluate_node_id_value_pairs' is never used.

(unusedFunction)

</details>

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between cdead62c99332f0cba054d4c8240d7b7fb5b43a5 and 9f21d38e063cddbfb3d8edf10945858c150c650f.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (1)</summary>

<details>
<summary>`**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.</summary>

> `**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.

- `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp`

</details>

</details><details>
<summary>🧠 Learnings (1)</summary>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp (1)</summary>

```
Learnt from: LinZhihao-723
PR: y-scope/clp#873
File: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp:148-157
Timestamp: 2025-05-02T22:27:59.347Z
Learning: In the `QueryHandlerImpl.cpp` file, the `unique_projected_columns` set (using `std::string_view`) is intentionally designed to only check for duplications within the local scope of the `create_projected_columns_and_projection_map` function. The team decided this is an acceptable use of `std::string_view` in a container since the referenced strings remain valid throughout the function's execution.
```

</details>

</details><details>
<summary>🪛 Cppcheck (2.10-2)</summary>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp</summary>

[style] 326-326: The function 'evaluate_node_id_value_pairs' is never used.

(unusedFunction)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (11)</summary>

* GitHub Check: ubuntu-jammy-static-linked-bins
* GitHub Check: ubuntu-jammy-dynamic-linked-bins
* GitHub Check: ubuntu-jammy-lint
* GitHub Check: centos-stream-9-static-linked-bins
* GitHub Check: centos-stream-9-dynamic-linked-bins
* GitHub Check: build-macos (macos-14, false)
* GitHub Check: build-macos (macos-14, true)
* GitHub Check: build-macos (macos-13, false)
* GitHub Check: build-macos (macos-13, true)
* GitHub Check: lint-check (ubuntu-latest)
* GitHub Check: lint-check (macos-latest)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T21:06:33Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T21:07:42Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T21:08:21Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T21:08:25Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T21:08:41Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T21:08:45Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-04T21:09:05Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T21:09:05Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-04T21:09:28Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-05T00:21:45Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-05T00:22:57Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>♻️ Duplicate comments (6)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/utils.hpp (2)</summary><blockquote>

`15-17`: **Deep relative include path was already flagged earlier**  

The five-level `../../../../../` include makes the build fragile and hard to read.  
A future PR is planned to fix this globally, so no action is required right now – just keeping the note alive.

---

`55-59`: **Doxygen entry still has a blank `@param`**  

The second `@param` tag is empty, which clang-doc and IDEs continue to warn about.  
Please either remove the tag or fill in the missing description.

</blockquote></details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp (3)</summary><blockquote>

`301-306`: _🧹 Nitpick (assertive)_

**Variable shadowing hampers readability**  

The structured binding re-uses the outer `node_id`, hiding it inside the loop.

```diff
-for (auto const [node_id, token_it] : new_partial_resolutions) {
+for (auto const [child_node_id, token_it] : new_partial_resolutions) {
-    auto [it, inserted] = partial_resolutions_to_update.try_emplace(
-            node_id,
+    auto [it, inserted] = partial_resolutions_to_update.try_emplace(
+            child_node_id,
```

---

`285-289`: _⚠️ Potential issue_

**String comparison ignores the `m_case_sensitive_search` flag**  

`token_it.get_token() != node_locator.get_key_name()` performs a strict, case-sensitive compare.  
When `m_case_sensitive_search` is `false`, legitimate case-insensitive matches will be missed,
breaking column resolution.

```diff
-        if (token_it.is_last() || token_it.get_token() != node_locator.get_key_name()) {
+        bool tokens_equal = m_case_sensitive_search
+                            ? token_it.get_token() == node_locator.get_key_name()
+                            : utils::iequals(token_it.get_token(), node_locator.get_key_name());
+        if (token_it.is_last() || false == tokens_equal) {
```
(N.B. `utils::iequals` already exists in `utils.hpp`; if not, an equivalent helper should be used.)

---

`58-83`: _🧹 Nitpick (assertive)_

**Mark trivial accessors `noexcept` for clarity & optimisation**  

`is_last`, `is_wildcard`, `is_trailing_wildcard`, `get_token`, and
`get_column_descriptor` cannot throw.  Declaring them `noexcept` advertises this
fact to callers and may unlock additional compiler optimisations.

```diff
-        [[nodiscard]] auto is_last() const -> bool {
+        [[nodiscard]] auto is_last() const noexcept -> bool {
...
-        [[nodiscard]] auto is_wildcard() const -> bool { return m_curr_token_it->wildcard(); }
+        [[nodiscard]] auto is_wildcard() const noexcept -> bool { return m_curr_token_it->wildcard(); }
...
-        [[nodiscard]] auto is_trailing_wildcard() const -> bool {
+        [[nodiscard]] auto is_trailing_wildcard() const noexcept -> bool {
...
-        [[nodiscard]] auto get_token() const -> std::string_view {
+        [[nodiscard]] auto get_token() const noexcept -> std::string_view {
...
-        [[nodiscard]] auto get_column_descriptor() const -> clp_s::search::ast::ColumnDescriptor* {
+        [[nodiscard]] auto get_column_descriptor() const noexcept -> clp_s::search::ast::ColumnDescriptor* {
```

</blockquote></details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp (1)</summary><blockquote>

`294-296`: _⚠️ Potential issue_

**Add a sanity check that KQL parsing succeeded**

The `parse_kql_expression` function can return `nullptr` if parsing fails, but there's no check before using the result. This could lead to segmentation faults if the KQL query is malformed.



Add a check after parsing to ensure the pointer is valid:

```diff
auto query_stream{std::istringstream{kql_query_str}};
auto query{clp_s::search::kql::parse_kql_expression(query_stream)};
+REQUIRE(query != nullptr);  // Ensure parsing did not fail
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 9f21d38e063cddbfb3d8edf10945858c150c650f and e7ef52e881390260ed33c46d825967a7a753da3b.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/test/utils.cpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/test/utils.hpp` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (1)</summary>

<details>
<summary>`**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.</summary>

> `**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.

- `components/core/src/clp/ffi/ir_stream/search/test/utils.cpp`
- `components/core/src/clp/ffi/ir_stream/search/test/utils.hpp`
- `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp`
- `components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp`

</details>

</details><details>
<summary>🧠 Learnings (2)</summary>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/utils.cpp (2)</summary>

```
Learnt from: LinZhihao-723
PR: y-scope/clp#873
File: components/core/src/clp/ffi/ir_stream/search/test/utils.cpp:78-84
Timestamp: 2025-05-02T23:49:30.798Z
Learning: The function `trivial_new_projected_schema_tree_node_callback` in the KV-IR codebase is documented in the header file to explain its purpose for testing, which addresses static analyzer warnings about unused functions.
```

```
Learnt from: LinZhihao-723
PR: y-scope/clp#873
File: components/core/src/clp/ffi/ir_stream/search/test/utils.cpp:78-84
Timestamp: 2025-05-02T23:49:30.798Z
Learning: The function `trivial_new_projected_schema_tree_node_callback` in the KV-IR codebase is properly documented in the header file to indicate its purpose for testing, which addresses static analyzer warnings about unused functions.
```

</details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/utils.hpp (1)</summary>

```
Learnt from: LinZhihao-723
PR: y-scope/clp#873
File: components/core/src/clp/ffi/ir_stream/search/test/utils.hpp:15-18
Timestamp: 2025-05-02T22:11:55.711Z
Learning: The team plans to systematically improve include paths by adding `${PROJECT_SOURCE_DIR}/src` to CMake targets and using direct include paths instead of deep relative ones in a future PR.
```

</details>

</details><details>
<summary>🪛 Cppcheck (2.10-2)</summary>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/utils.cpp</summary>

[style] 67-67: The function 'trivial_new_projected_schema_tree_node_callback' is never used.

(unusedFunction)

---

[style] 75-75: The function 'get_schema_tree_column_queries' is never used.

(unusedFunction)

</details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp</summary>

[error] 207-207: syntax error

(syntaxError)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (10)</summary>

* GitHub Check: ubuntu-jammy-dynamic-linked-bins
* GitHub Check: ubuntu-jammy-static-linked-bins
* GitHub Check: ubuntu-jammy-lint
* GitHub Check: centos-stream-9-dynamic-linked-bins
* GitHub Check: centos-stream-9-static-linked-bins
* GitHub Check: build-macos (macos-15, false)
* GitHub Check: build-macos (macos-15, true)
* GitHub Check: build-macos (macos-13, true)
* GitHub Check: build-macos (macos-14, false)
* GitHub Check: lint-check (macos-latest)

</details>

<details>
<summary>🔇 Additional comments (4)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp (4)</summary>

`179-179`: **LGTM! Good validation of non-empty projectable nodes.**

The check ensures that projectable node IDs are not empty before proceeding, which helps prevent issues with empty collections later in the code.

---

`89-90`: **LGTM! The code correctly follows the coding guideline.**

The expression pattern `false == matchable_node_ids.empty()` follows the project's coding guideline to prefer `false == <expression>` rather than `!<expression>`.

---

`212-227`: **LGTM! Clear schema visualization with ASCII art.**

The ASCII art diagram provides a valuable visual representation of the schema tree structure, making it easier to understand the test setup and the relationships between nodes.

---

`414-418`: **LGTM! Good error handling with clear comments.**

The code properly checks for errors when creating the QueryHandlerImpl instance and includes clear comments explaining the NOLINTNEXTLINE directive, referencing the relevant GitHub issue.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T00:25:23Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-05T01:10:13Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T01:10:42Z
user: login: LinZhihao-723
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-05T01:11:44Z
user: login: coderabbitai[bot]
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T01:12:19Z
user: login: gibber9809
body: Some minor comments, but otherwise the tests look very good and the column resolution code is looking great!
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T17:06:26Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 4**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/utils.hpp (1)</summary><blockquote>

`64-73`: **Incomplete Doxygen parameter documentation**

The function documentation has an empty `@param` entry for `node_id` and is missing documentation for `projected_key_path`.

```diff
/**
 * Trivial implementation of `NewProjectedSchemaTreeNodeCallback` that always return success without
 * doing anything.
 * @param is_auto_generated Whether the node belongs to the auto-generated namespace.
-* @param
+* @param node_id The schema tree node ID receiving the projection.
+* @param projected_key_path The full key-path being projected (unescaped).
 */
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 1a4589de5624093875e59ae300de8f308233672b and 7005a756eb01d71d792fea1f0ed44567f6abdf7d.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `components/core/src/clp/ffi/ir_stream/search/test/utils.hpp` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (1)</summary>

<details>
<summary>`**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.</summary>

> `**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.

- `components/core/src/clp/ffi/ir_stream/search/test/utils.hpp`

</details>

</details><details>
<summary>🧠 Learnings (1)</summary>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/utils.hpp (1)</summary>

```
Learnt from: LinZhihao-723
PR: y-scope/clp#873
File: components/core/src/clp/ffi/ir_stream/search/test/utils.hpp:15-18
Timestamp: 2025-05-02T22:11:55.711Z
Learning: The team plans to systematically improve include paths by adding `${PROJECT_SOURCE_DIR}/src` to CMake targets and using direct include paths instead of deep relative ones in a future PR.
```

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (11)</summary>

* GitHub Check: ubuntu-jammy-dynamic-linked-bins
* GitHub Check: ubuntu-jammy-static-linked-bins
* GitHub Check: ubuntu-jammy-lint
* GitHub Check: centos-stream-9-dynamic-linked-bins
* GitHub Check: centos-stream-9-static-linked-bins
* GitHub Check: build-macos (macos-14, true)
* GitHub Check: build-macos (macos-13, false)
* GitHub Check: build-macos (macos-15, true)
* GitHub Check: build-macos (macos-14, false)
* GitHub Check: lint-check (ubuntu-latest)
* GitHub Check: lint-check (macos-latest)

</details>

<details>
<summary>🔇 Additional comments (1)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/utils.hpp (1)</summary>

`69-73`: <details>
<summary>✅ Verification successful</summary>

**Review coding guidelines for conditional checks**

According to your coding guidelines, conditional checks should prefer `false == <expression>` rather than `!<expression>`. While this function doesn't contain explicit conditional checks, ensure this guideline is followed in the implementation when checking if `is_auto_generated` is false.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if the implementation of this function follows the coding guidelines
# for conditional checks by looking for the pattern "!is_auto_generated"

grep -r "!is_auto_generated" components/core/src/clp/ffi/ir_stream/search/test/utils.cpp
```

Length of output: 90

---

To inspect how `is_auto_generated` is actually checked in the implementation, let’s locate the function definition and any uses of that parameter:

---

🏁 Script executed:

```shell
#!/bin/bash
# Show the implementation of trivial_new_projected_schema_tree_node_callback
grep -R "trivial_new_projected_schema_tree_node_callback" \
  components/core/src/clp/ffi/ir_stream/search/test/utils.cpp -n -A5 -B5

# List all occurrences of the parameter to see any conditional checks
grep -R "is_auto_generated" \
  components/core/src/clp/ffi/ir_stream/search/test/utils.cpp -n -A2 -B2
```

Length of output: 916

---



</details>

**No conditional checks in `trivial_new_projected_schema_tree_node_callback` – no action required**

The implementation of this function doesn’t reference `is_auto_generated` (it’s marked `[[maybe_unused]]` and immediately returns `success()`), so there are currently no `!is_auto_generated` checks to replace. No changes are needed here.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T17:44:19Z
user: login: gibber9809
body: LGTM, nice work! PR title also looks good to me.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T18:52:52Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>♻️ Duplicate comments (5)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/utils.hpp (1)</summary><blockquote>

`67-67`: **Missing parameter documentation**

This line contains an empty `@param` tag.

```diff
-* @param
+* @param node_id The ID of the schema tree node receiving the projection
+* @param projected_key_path The full key path being projected (unescaped)
```

</blockquote></details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp (1)</summary><blockquote>

`120-137`: **Consider flattening chained assignment in if statements**

The current implementation combines transformation and null check in single if statements, which couples concerns and makes debugging more challenging.

```diff
-if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
-    nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
-{
-    return query;
-}
+auto transformed{clp_s::search::ast::OrOfAndForm{}.run(query)};
+if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(transformed)) {
+    return transformed;
+}
+query = std::move(transformed);
```

This pattern also applies to the NarrowTypes transformation below.

</blockquote></details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp (3)</summary><blockquote>

`294-296`: **Add a null check after KQL parsing**

The KQL parser can return `nullptr` if parsing fails. Adding a check ensures the test fails early with a clear error message rather than crashing on a null dereference later.

```diff
auto query_stream{std::istringstream{kql_query_str}};
auto query{clp_s::search::kql::parse_kql_expression(query_stream)};
+REQUIRE(query != nullptr);  // Verify that KQL parsing succeeded
```

---

`76-163`: **Consider refactoring repetitive code blocks in generate_matchable_kql_expressions**

The function contains nearly identical code blocks for handling each data type (Int, Float, Bool, Str). Consider using a helper function to reduce duplication:

```cpp
auto process_type = [&](SchemaTree::Node::Type type, auto ref_value, std::string_view format = "{}") {
    for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
        auto const column_query_with_namespace{fmt::format("{}{}", column_namespace, column_query)};
        if (auto const matchable_node_ids{
                    possible_matches.get_matchable_node_ids_from_schema_tree_type(type)
                };
                false == matchable_node_ids.empty())
        {
            matchable_kql_expressions.emplace_back(
                    fmt::format("{}: " + std::string{format}, column_query_with_namespace, ref_value)
            );
            auto [it, inserted] = expected_column_resolutions.try_emplace(
                    column_query_with_namespace,
                    std::set<SchemaTree::Node::id_t>{}
            );
            auto& resolved_node_ids{it->second};
            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
        }
    }
};

// Process each type
process_type(SchemaTree::Node::Type::Int, cRefTestInt);
process_type(SchemaTree::Node::Type::Float, cRefTestFloat, "{:.2f}");
process_type(SchemaTree::Node::Type::Bool, cRefTestBool);
process_type(SchemaTree::Node::Type::Str, cRefTestStr);
```

This reduces duplication and improves maintainability.

---

`228-246`: **Consider extracting duplicated schema tree setup into a helper function**

Both test cases duplicate the same schema tree setup code. Consider extracting this into a helper function:

```cpp
namespace {
// Returns a schema tree with a predefined structure and the corresponding node locators
[[nodiscard]] auto create_test_schema_tree() 
    -> std::pair<std::shared_ptr<SchemaTree>, std::vector<SchemaTree::NodeLocator>> 
{
    auto schema_tree{std::make_shared<SchemaTree>()};
    std::vector<SchemaTree::NodeLocator> const locators{
            {SchemaTree::cRootId, "a", SchemaTree::Node::Type::Obj},
            {SchemaTree::cRootId, "b", SchemaTree::Node::Type::Int},
            {1, "b", SchemaTree::Node::Type::Obj},
            // ... (other locators)
    };
    for (auto const& locator : locators) {
        schema_tree->insert_node(locator);
    }
    return {schema_tree, locators};
}
}
```

Then in each test case:
```cpp
auto [schema_tree, locators] = create_test_schema_tree();
```



Also applies to: 369-387

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 7005a756eb01d71d792fea1f0ed44567f6abdf7d and a2868f990b7043a825040047ab847d1b8602d225.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp` (1 hunks)
* `components/core/src/clp/ffi/ir_stream/search/test/utils.hpp` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (1)</summary>

<details>
<summary>`**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.</summary>

> `**/*.{cpp,hpp,java,js,jsx,tpp,ts,tsx}`: - Prefer `false == <expression>` rather than `!<expression>`.

- `components/core/src/clp/ffi/ir_stream/search/test/utils.hpp`
- `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp`
- `components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp`

</details>

</details><details>
<summary>🧠 Learnings (2)</summary>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/utils.hpp (1)</summary>

```
Learnt from: LinZhihao-723
PR: y-scope/clp#873
File: components/core/src/clp/ffi/ir_stream/search/test/utils.hpp:15-18
Timestamp: 2025-05-02T22:11:55.711Z
Learning: The team plans to systematically improve include paths by adding `${PROJECT_SOURCE_DIR}/src` to CMake targets and using direct include paths instead of deep relative ones in a future PR.
```

</details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp (1)</summary>

```
Learnt from: LinZhihao-723
PR: y-scope/clp#873
File: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp:148-157
Timestamp: 2025-05-02T22:27:59.347Z
Learning: In the `QueryHandlerImpl.cpp` file, the `unique_projected_columns` set (using `std::string_view`) is intentionally designed to only check for duplications within the local scope of the `create_projected_columns_and_projection_map` function. The team decided this is an acceptable use of `std::string_view` in a container since the referenced strings remain valid throughout the function's execution.
```

</details>

</details><details>
<summary>🪛 Cppcheck (2.10-2)</summary>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp</summary>

[style] 323-323: The function 'evaluate_node_id_value_pairs' is never used.

(unusedFunction)

</details>
<details>
<summary>components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp</summary>

[error] 207-207: syntax error

(syntaxError)

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (5)</summary>

* GitHub Check: ubuntu-jammy-dynamic-linked-bins
* GitHub Check: centos-stream-9-static-linked-bins
* GitHub Check: centos-stream-9-dynamic-linked-bins
* GitHub Check: ubuntu-jammy-static-linked-bins
* GitHub Check: ubuntu-jammy-lint

</details>

<details>
<summary>🔇 Additional comments (2)</summary><blockquote>

<details>
<summary>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp (2)</summary>

`321-328`: **WIP implementation clearly marked**

The stub implementation of `evaluate_node_id_value_pairs` with the TODO comment appropriately marks this as work-in-progress. This aligns well with the PR objectives of providing a partial implementation.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Cppcheck (2.10-2)</summary>

[style] 323-323: The function 'evaluate_node_id_value_pairs' is never used.

(unusedFunction)

</details>

</details>

---

`157-165`: **Good use of the coding guideline pattern**

Correctly using `false == expression` pattern rather than `!expression` as recommended in the coding guidelines.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-05T18:53:22Z

Review comments:
diff_hunk: @@ -0,0 +1,340 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param colum_descriptor
+         * @return A result containing the constructed token iterator on success, or an error code
+         * indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the column doesn't have any
+         *   token.
+         */
+        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* colum_descriptor)
+                -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            auto const token_begin_it{colum_descriptor->descriptor_begin()};
+            if (colum_descriptor->descriptor_end() == token_begin_it) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound};
+            }
+            return ColumnDescriptorTokenIterator{colum_descriptor, token_begin_it};
+        }
+
+        // Methods
+        /**
+         * @return Whether this is the last token of the underlying column descriptor.
+         */
+        [[nodiscard]] auto is_last() const -> bool {
+            return m_column_descriptor->descriptor_end() == m_next_token_it;
+        }
+
+        /**
+         * @return A result containing a newly constructor iterator pointing to the next token on
+         * success, or an error code indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the current token is already
+         *   the last one.
+         */
+        [[nodiscard]] auto next() const -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            if (is_last()) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound};
+            }
+            return ColumnDescriptorTokenIterator{m_column_descriptor, m_next_token_it};
+        }
+
+        [[nodiscard]] auto is_wildcard() const -> bool { return m_curr_token_it->wildcard(); }
+
+        [[nodiscard]] auto get_token() const -> std::string_view {
+            return m_curr_token_it->get_token();
+        }
+
+        [[nodiscard]] auto get_column_descriptor() const -> clp_s::search::ast::ColumnDescriptor* {
+            return m_column_descriptor;
+        }
+
+        /**
+         * @param type
+         * @return Whether the underlying column can match the given schema-tree node type.
+         */
+        // TODO: Fix clang-tidy
+        // NOLINTNEXTLINE(*)
+        [[nodiscard]] auto match(SchemaTree::Node::Type type) const -> bool {
+            // TODO: Implement this method after #856 is merged.
+            return false;
+        }
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`match` always returns `false`, breaking column resolution**

`handle_column_resolution_on_leaf_node` relies on `token_it.match(...)` to decide whether a leaf matches the descriptor.  
With the current stub, no column will ever resolve, so projections will never succeed.

At minimum, return `true` with a TODO, or gate the call until #856 lands; otherwise the rest of the handler behaves as a no-op.


```diff
-            // TODO: Implement this method after #856 is merged.
-            return false;
+            // TODO(#856): implement proper matching logic.
+            return true;  // temporary to keep resolution functional
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
        // TODO: Fix clang-tidy
        // NOLINTNEXTLINE(*)
        [[nodiscard]] auto match(SchemaTree::Node::Type type) const -> bool {
            // TODO(#856): implement proper matching logic.
            return true;  // temporary to keep resolution functional
        }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commits 13e67ab to d651b1b
created_at: 2025-05-01T00:02:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,385 @@
+#include "utils.hpp"
+
+#include <concepts>
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+
+#include <outcome/outcome.hpp>
+#include <string_utils/string_utils.hpp>
+
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/FilterOperation.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../ir/EncodedTextAst.hpp"
+#include "../../SchemaTree.hpp"
+#include "../../Value.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::FilterOperation;
+using clp_s::search::ast::Literal;
+using clp_s::search::ast::LiteralType;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Evaluates a numerical filter operation by applying the specified `FilterOperation` to two
+ * operands.
+ * @tparam OperandType The type of the operands, must be either `value_int_t` or `value_float_t`.
+ * @param op
+ * @param filter_operand The operand associated with the filter.
+ * @param value_operand The value operand to evaluate.
+ * @return Whether the filter condition is satisfied.
+ */
+template <typename OperandType>
+requires std::same_as<OperandType, value_int_t> || std::same_as<OperandType, value_float_t>
+[[nodiscard]] auto evaluate_numerical_filter_op(
+        FilterOperation op,
+        OperandType filter_operand,
+        OperandType value_operand
+) -> bool;
+
+/**
+ * Evaluates a string filter operation by applying the specified `FilterOperation` to two string
+ * operands.
+ * @param op
+ * @param filter_operand The operand associated with the filter.
+ * @param value_operand The value operand to evaluate.
+ * @return Whether the filter condition is satisfied.
+ */
+[[nodiscard]] auto evaluate_string_filter_op(
+        FilterOperation op,
+        std::string_view filter_operand,
+        std::string_view value_operand,
+        bool case_sensitive_match
+) -> bool;
+
+/**
+ * Evaluates an integer filter operation by applying the specified `FilterOperation` to the given
+ * filter operand and value.
+ * @param op
+ * @param operand
+ * @param value
+ * @return Whether the filter condition is satisfied.
+ */
+[[nodiscard]] auto evaluate_int_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value
+) -> bool;
+
+/**
+ * Evaluates a float filter operation by applying the specified `FilterOperation` to the given
+ * filter operand and value.
+ * @param op
+ * @param operand
+ * @param value
+ * @return Whether the filter condition is satisfied.
+ */
+[[nodiscard]] auto evaluate_float_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value
+) -> bool;
+
+/**
+ * Evaluates a boolean filter operation by applying the specified `FilterOperation` to the given
+ * operand and value.
+ * @param op
+ * @param operand
+ * @param value
+ * @return Whether the filter condition is satisfied.
+ */
+[[nodiscard]] auto evaluate_bool_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value
+) -> bool;
+
+/**
+ * Evaluates a `VarString` filter operation by applying the specified `FilterOperation` to the given
+ * operand and value.
+ * @param op
+ * @param operand
+ * @param value
+ * @return Whether the filter condition is satisfied.
+ */
+[[nodiscard]] auto evaluate_var_string_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value,
+        bool case_sensitive_match
+) -> bool;
+
+/**
+ * Evaluates a `ClpString` filter operation by applying the specified `FilterOperation` to the given
+ * operand and value.
+ * @param op
+ * @param operand
+ * @param value
+ * @return A result containing a boolean indicating whether the filter condition is satisfied on
+ * success, or an error code indicating the failure:
+ * - ErrorCodeEnum::EncodedTextAstDecodingFailure if failed to decode the given encoded text AST.
+ */
+[[nodiscard]] auto evaluate_clp_string_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value,
+        bool case_sensitive_match
+) -> outcome_v2::std_result<bool>;
+
+template <typename OperandType>
+requires std::same_as<OperandType, value_int_t> || std::same_as<OperandType, value_float_t>
+auto evaluate_numerical_filter_op(
+        FilterOperation op,
+        OperandType filter_operand,
+        OperandType value_operand
+) -> bool {
+    switch (op) {
+        case FilterOperation::EQ:
+            return value_operand == filter_operand;
+        case FilterOperation::NEQ:
+            return value_operand != filter_operand;
+        case FilterOperation::LT:
+            return value_operand < filter_operand;
+        case FilterOperation::GT:
+            return value_operand > filter_operand;
+        case FilterOperation::LTE:
+            return value_operand <= filter_operand;
+        case FilterOperation::GTE:
+            return value_operand >= filter_operand;
+        default:
+            return false;
+    }
+}
+
+auto evaluate_string_filter_op(
+        FilterOperation op,
+        std::string_view filter_operand,
+        std::string_view value_operand,
+        bool case_sensitive_match
+) -> bool {
+    switch (op) {
+        case FilterOperation::EQ:
+            return clp::string_utils::wildcard_match_unsafe(
+                    value_operand,
+                    filter_operand,
+                    case_sensitive_match
+            );
+        case FilterOperation::NEQ:
+            return false
+                   == clp::string_utils::wildcard_match_unsafe(
+                           value_operand,
+                           filter_operand,
+                           case_sensitive_match
+                   );
+        default:
+            return false;
+    }
+}
+
+auto evaluate_int_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value
+) -> bool {
+    value_int_t filter_operand{};
+    if (false == operand->as_int(filter_operand, op)) {
+        return false;
+    }
+    return evaluate_numerical_filter_op(
+            op,
+            filter_operand,
+            value.get_immutable_view<value_int_t>()
+    );
+}
+
+auto evaluate_float_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value
+) -> bool {
+    value_float_t filter_operand{};
+    if (false == operand->as_float(filter_operand, op)) {
+        return false;
+    }
+    return evaluate_numerical_filter_op(
+            op,
+            filter_operand,
+            value.get_immutable_view<value_float_t>()
+    );
+}
+
+auto evaluate_bool_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value
+) -> bool {
+    bool filter_operand{};
+    if (false == operand->as_bool(filter_operand, op)) {
+        return false;
+    }
+    auto const value_operand{value.get_immutable_view<clp::ffi::value_bool_t>()};
+
+    switch (op) {
+        case FilterOperation::EQ:
+            return value_operand == filter_operand;
+        case FilterOperation::NEQ:
+            return value_operand != filter_operand;
+        default:
+            return false;
+    }
+}
+
+auto evaluate_var_string_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value,
+        bool case_sensitive_match
+) -> bool {
+    std::string filter_operand;
+    if (false == operand->as_var_string(filter_operand, op)) {
+        return false;
+    }
+    auto const value_operand{value.get_immutable_view<std::string>()};
+
+    return evaluate_string_filter_op(op, filter_operand, value_operand, case_sensitive_match);
+}
+
+auto evaluate_clp_string_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value,
+        bool case_sensitive_match
+) -> outcome_v2::std_result<bool> {
+    std::string filter_operand;
+    if (false == operand->as_clp_string(filter_operand, op)) {
+        return false;
+    }
+
+    auto const optional_value_operand{
+            value.is<clp::ir::EightByteEncodedTextAst>()
+                    ? value.get_immutable_view<clp::ir::EightByteEncodedTextAst>()
+                              .decode_and_unparse()
+                    : value.get_immutable_view<clp::ir::FourByteEncodedTextAst>()
+                              .decode_and_unparse()
+    };
path: components/core/src/clp/ffi/ir_stream/search/utils.cpp
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Potential undefined behaviour when value is neither 4- nor 8-byte AST**

The ternary operator assumes that any non-`EightByteEncodedTextAst` value is a `FourByteEncodedTextAst`.  
If a third representation (or a plain string disguised as `ClpStringT`) slips through, the `.get_immutable_view<…>()` call will access the variant with an incorrect type, leading to UB.

Consider guarding with `value.is<FourByteEncodedTextAst>()` and returning `LiteralTypeUnexpected` (or similar) on a mismatch.

---

🏁 Script executed:

```shell
#!/bin/bash
# Display the surrounding context in utils.cpp to identify the function and type of `value`
sed -n '240,320p' components/core/src/clp/ffi/ir_stream/search/utils.cpp
```

Length of output: 3025

---

🏁 Script executed:

```shell
#!/bin/bash
# Search for the Value type definition to inspect its variant alternatives
rg -n "class Value" -R components/core
rg -n "struct Value" -R components/core
```

Length of output: 892

---

🏁 Script executed:

```shell
#!/bin/bash
# Locate and display the definition of `Value` to inspect its variant alternatives
path=$(rg -l "class Value" -g "*.hpp" . | head -n1)
if [[ -z "$path" ]]; then
  path=$(rg -l "struct Value" -g "*.hpp" . | head -n1)
fi
echo "Inspecting Value definition in: $path"
sed -n '1,200p' "$path"
```

Length of output: 2806

---

🏁 Script executed:

```shell
#!/bin/bash
# Show includes and namespace declarations in utils.cpp to locate Value’s definition
sed -n '1,200p' components/core/src/clp/ffi/ir_stream/search/utils.cpp
```

Length of output: 6307

---

🏁 Script executed:

```shell
#!/bin/bash
# Locate and display the definition of `Value` in the clp/ffi directory
value_path=$(rg -l "class Value" -g "Value.hpp" components/core/src/clp/ffi | head -n1)
echo "Found Value.hpp at: $value_path"
sed -n '1,200p' "$value_path"
```

Length of output: 5843

---



</details>

**Guard against non‐AST variants in evaluate_clp_string_filter_op**  
Currently, if `value` isn’t an `EightByteEncodedTextAst`, we unconditionally fall back to `FourByteEncodedTextAst`. When `value` holds any other alternative (e.g. `std::string`, `value_int_t`, etc.), the subsequent  
```cpp
value.get_immutable_view<clp::ir::FourByteEncodedTextAst>()
```
will throw `Value::OperationFailed`, escaping the intended `std_result<bool>` error-path.

• In components/core/src/clp/ffi/ir_stream/search/utils.cpp, within `evaluate_clp_string_filter_op` (around line 262), replace the single ternary with an explicit if/else chain:  
```cpp
std::optional<std::string> optional_value_operand;
if (value.is<clp::ir::EightByteEncodedTextAst>()) {
    optional_value_operand =
        value.get_immutable_view<clp::ir::EightByteEncodedTextAst>()
             .decode_and_unparse();
}
else if (value.is<clp::ir::FourByteEncodedTextAst>()) {
    optional_value_operand =
        value.get_immutable_view<clp::ir::FourByteEncodedTextAst>()
             .decode_and_unparse();
}
else {
    // Neither encoded‐text AST—return a clear error instead of throwing.
    return ErrorCode{ErrorCodeEnum::LiteralTypeUnexpected};
}
if (!optional_value_operand.has_value()) {
    return ErrorCode{ErrorCodeEnum::EncodedTextAstDecodingFailure};
}
```

• This ensures that only the two supported AST types are decoded, and all other variants return a controlled error rather than uncaught exceptions.

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-01T18:13:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,344 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param colum_descriptor
+         * @return A result containing the constructed token iterator on success, or an error code
+         * indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the column doesn't have any
+         *   token.
+         */
+        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* colum_descriptor)
+                -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            auto const token_begin_it{colum_descriptor->descriptor_begin()};
+            if (colum_descriptor->descriptor_end() == token_begin_it) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound};
+            }
+            return ColumnDescriptorTokenIterator{colum_descriptor, token_begin_it};
+        }
+
+        // Methods
+        /**
+         * @return Whether this is the last token of the underlying column descriptor.
+         */
+        [[nodiscard]] auto is_last() const -> bool {
+            return m_column_descriptor->descriptor_end() == m_next_token_it;
+        }
+
+        /**
+         * @return A result containing a newly constructor iterator pointing to the next token on
+         * success, or an error code indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the current token is already
+         *   the last one.
+         */
+        [[nodiscard]] auto next() const -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            if (is_last()) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound};
+            }
+            return ColumnDescriptorTokenIterator{m_column_descriptor, m_next_token_it};
+        }
+
+        [[nodiscard]] auto is_wildcard() const -> bool { return m_curr_token_it->wildcard(); }
+
+        [[nodiscard]] auto get_token() const -> std::string_view {
+            return m_curr_token_it->get_token();
+        }
+
+        [[nodiscard]] auto get_column_descriptor() const -> clp_s::search::ast::ColumnDescriptor* {
+            return m_column_descriptor;
+        }
+
+        /**
+         * @param type
+         * @return Whether the underlying column can match the given schema-tree node type.
+         */
+        [[nodiscard]] auto match(SchemaTree::Node::Type type) const -> bool {
+            return m_column_descriptor->matches_any(schema_tree_node_type_to_literal_types(type));
+        }
+
+    private:
+        // Constructor
+        ColumnDescriptorTokenIterator(
+                clp_s::search::ast::ColumnDescriptor* colum_descriptor,
+                clp_s::search::ast::DescriptorList::iterator curr_token_it
+        )
+                : m_column_descriptor{colum_descriptor},
+                  m_curr_token_it{curr_token_it},
+                  m_next_token_it{curr_token_it + 1} {}
+
+        clp_s::search::ast::ColumnDescriptor* m_column_descriptor;
+        clp_s::search::ast::DescriptorList::iterator m_curr_token_it;
+        clp_s::search::ast::DescriptorList::iterator m_next_token_it;
+    };
+
+    using ProjectionMap = std::unordered_map<clp_s::search::ast::ColumnDescriptor*, std::string>;
+
+    using PartialResolutionMap = std::
+            unordered_map<SchemaTree::Node::id_t, std::vector<ColumnDescriptorTokenIterator>>;
+
+    // Factory function
+    /**
+     * @param query The search query.
+     * @param projections The columns to project.
+     * @param case_sensitive_match Whether to use case-sensitive match for string comparison.
+     * @return A result containing the newly constructed `QueryHandler` on success, or an error code
+     * indicating the failure:
+     * - Forwards `preprocess_query`'s return values.
+     * - Forwards `create_projected_columns_and_projection_map`'s return values.
+     * - Forwards `create_initial_partial_resolutions`'s return values.
+     */
+    [[nodiscard]] static auto create(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            std::vector<std::pair<std::string, clp_s::search::ast::LiteralTypeBitmask>> const&
+                    projections,
+            bool case_sensitive_match
+    ) -> outcome_v2::std_result<QueryHandlerImpl>;
+
+    // Delete copy constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl const&) = delete;
+    auto operator=(QueryHandlerImpl const&) -> QueryHandlerImpl& = delete;
+
+    // Default move constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl&&) = default;
+    auto operator=(QueryHandlerImpl&&) -> QueryHandlerImpl& = default;
+
+    // Destructor
+    ~QueryHandlerImpl() = default;
+
+    /**
+     * Implementation of `QueryHandler::evaluate_node_id_value_pairs`.
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - TODO
+     */
+    [[nodiscard]] auto evaluate_node_id_value_pairs(
+            KeyValuePairLogEvent::NodeIdValuePairs const& auto_gen_node_id_value_pairs,
+            KeyValuePairLogEvent::NodeIdValuePairs const& user_gen_node_id_value_pairs
+    ) -> outcome_v2::std_result<AstEvaluationResult>;
+
+    /**
+     * Implementation of `QueryHandler::update_partially_resolved_columns` with new projected
+     * schema-tree node callback given as a template parameter.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @param new_projected_schema_tree_node_callback
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `handle_column_resolution_on_leaf_node`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto update_partially_resolved_columns(
+            bool is_auto_generated,
+            SchemaTree::NodeLocator const& node_locator,
+            SchemaTree::Node::id_t node_id,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    [[nodiscard]] auto get_resolved_column_to_schema_tree_node_ids() const -> std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::vector<SchemaTree::Node::id_t>> {
+        return m_resolved_column_to_schema_tree_node_ids;
+    }
+
+private:
+    // Constructor
+    QueryHandlerImpl(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            PartialResolutionMap auto_gen_namespace_partial_resolutions,
+            PartialResolutionMap user_gen_namespace_partial_resolutions,
+            std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> projected_columns,
+            ProjectionMap projected_column_to_original_key,
+            bool case_sensitive_match
+    )
+            : m_query{std::move(query)},
+              m_auto_gen_namespace_partial_resolutions{
+                      std::move(auto_gen_namespace_partial_resolutions)
+              },
+              m_user_gen_namespace_partial_resolutions{
+                      std::move(user_gen_namespace_partial_resolutions)
+              },
+              m_projected_columns{std::move(projected_columns)},
+              m_projected_column_to_original_key{std::move(projected_column_to_original_key)},
+              m_case_sensitive_search{case_sensitive_match} {}
+
+    // Methods
+    /**
+     * Handles column resolution on the given schema-tree leaf node and the token in the column.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param is_auto_generated
+     * @param leaf_node_id
+     * @param leaf_node_locator
+     * @param token_it The current resolved token in the column.
+     * @param new_projected_schema_tree_node_callback
+     * @return A void result on success, or an error code indicating the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `new_projected_schema_tree_node_callback`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto handle_column_resolution_on_leaf_node(
+            bool is_auto_generated,
+            SchemaTree::Node::id_t leaf_node_id,
+            SchemaTree::NodeLocator const& leaf_node_locator,
+            ColumnDescriptorTokenIterator const& token_it,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    // Variables
+    std::shared_ptr<clp_s::search::ast::Expression> m_query;
+    PartialResolutionMap m_auto_gen_namespace_partial_resolutions;
+    PartialResolutionMap m_user_gen_namespace_partial_resolutions;
+    std::unordered_map<clp_s::search::ast::ColumnDescriptor*, std::vector<SchemaTree::Node::id_t>>
+            m_resolved_column_to_schema_tree_node_ids;
+    std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> m_projected_columns;
+    ProjectionMap m_projected_column_to_original_key;
+    bool m_case_sensitive_search;
+};
+
+template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+auto QueryHandlerImpl::update_partially_resolved_columns(
+        bool is_auto_generated,
+        SchemaTree::NodeLocator const& node_locator,
+        SchemaTree::Node::id_t node_id,
+        NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+) -> outcome_v2::std_result<void> {
+    auto const parent_node_id{node_locator.get_parent_id()};
+    auto& partial_resolutions_to_update{
+            is_auto_generated ? m_auto_gen_namespace_partial_resolutions
+                              : m_user_gen_namespace_partial_resolutions
+    };
+    if (false == partial_resolutions_to_update.contains(parent_node_id)) {
+        return outcome_v2::success();
+    }
+
+    for (auto const& token_it : partial_resolutions_to_update.at(parent_node_id)) {
+        auto const next_token_it_result{token_it.next()};
+        if (SchemaTree::Node::Type::Obj == node_locator.get_type()) {
+            // Handle schema-tree non-leaf nodes.
+            if (token_it.is_last()) {
+                // TODO: Handle object search when supported.
+                continue;
+            }
+
+            if (token_it.is_wildcard()) {
+                auto [it, inserted] = partial_resolutions_to_update.try_emplace(
+                        node_id,
+                        std::vector<ColumnDescriptorTokenIterator>{}
+                );
+                it->second.emplace_back(token_it);
+                it->second.emplace_back(OUTCOME_TRYX(token_it.next()));
+                continue;
+            }
+
+            if (token_it.get_token() != node_locator.get_key_name()) {
+                continue;
+            }
+
+            auto const next_token_it{OUTCOME_TRYX(token_it.next())};
+            auto [it, inserted] = partial_resolutions_to_update.try_emplace(
+                    node_id,
+                    std::vector<ColumnDescriptorTokenIterator>{}
+            );
+            it->second.emplace_back(next_token_it);
+            if (false == next_token_it.is_last() && next_token_it.is_wildcard()) {
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`unordered_map` rehash may invalidate the vector we are iterating**

While iterating over `partial_resolutions_to_update.at(parent_node_id)` you insert new keys into the **same** `unordered_map`.  
`unordered_map::insert/try_emplace` can trigger a rehash, invalidating *all* references into the container – including the `vector` you are looping through.  This leads to use-after-iterator bugs.

A safe pattern is:

1. Copy the vector (or the iterators you need) into a temporary container.
2. Perform insertions based on that copy after the loop.

```diff
-auto const& tokens = partial_resolutions_to_update.at(parent_node_id);
-for (auto const& token_it : tokens) {
+auto tokens = partial_resolutions_to_update.at(parent_node_id);   // copy
+for (auto const& token_it : tokens) {
     …
}
```

Please refactor to avoid iterator invalidation.



> Committable suggestion skipped: line range outside the PR's diff.

<!-- This is an auto-generated reply by CodeRabbit -->

✅ Addressed in commits 0166fb8 to 141242c
created_at: 2025-05-01T18:13:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,501 @@
+#include <algorithm>
+#include <concepts>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <catch2/catch.hpp>
+
+#include "../../../../../clp_s/search/ast/BooleanLiteral.hpp"
+#include "../../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../../clp_s/search/ast/FilterOperation.hpp"
+#include "../../../../../clp_s/search/ast/Integral.hpp"
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../../clp_s/search/ast/StringLiteral.hpp"
+#include "../../../../ir/EncodedTextAst.hpp"
+#include "../../../../ir/types.hpp"
+#include "../../../encoding_methods.hpp"
+#include "../../../Value.hpp"
+#include "../utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp::ffi::ir_stream::search::evaluate_filter_against_literal_type_value_pair;
+using clp::ffi::Value;
+using clp::ffi::value_bool_t;
+using clp::ffi::value_float_t;
+using clp::ffi::value_int_t;
+using clp::ir::eight_byte_encoded_variable_t;
+using clp::ir::EightByteEncodedTextAst;
+using clp::ir::four_byte_encoded_variable_t;
+using clp::ir::FourByteEncodedTextAst;
+using clp_s::search::ast::BooleanLiteral;
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::FilterOperation;
+using clp_s::search::ast::Integral;
+using clp_s::search::ast::Literal;
+using clp_s::search::ast::LiteralType;
+using clp_s::search::ast::StringLiteral;
+
+using ValueToMatchedFilterOpsPair
+        = std::pair<std::optional<Value>, std::unordered_set<FilterOperation>>;
+
+constexpr std::string_view cRefTestString{"test"};
+
+/**
+ * Parses and encodes the given string as an instance of `EncodedTextAst`.
+ * @tparam encoded_variable_t
+ * @param text
+ * @return The encoded result.
+ */
+template <typename encoded_variable_t>
+requires std::is_same_v<encoded_variable_t, eight_byte_encoded_variable_t>
+         || std::is_same_v<encoded_variable_t, four_byte_encoded_variable_t>
+[[nodiscard]] auto get_encoded_text_ast(std::string_view text)
+        -> clp::ir::EncodedTextAst<encoded_variable_t>;
+
+/**
+ * Asserts the filter evaluation results match the expectation on the given values.
+ * @param filter_to_test
+ * @param filter_operand_literal_type
+ * @param value_to_matched_filter_ops_pairs A vector of pairs, where each pair consists of:
+ *   - A value to test against the filter.
+ *   - All of the filter operations that are expected to evaluate to `true` for this value.
+ * @return Whether all evaluations match their expected results.
+ */
+[[nodiscard]] auto assert_filter_evaluation_results_on_values(
+        FilterExpr const* filter_to_test,
+        LiteralType filter_operand_literal_type,
+        std::vector<ValueToMatchedFilterOpsPair> const& value_to_matched_filter_ops_pairs
+) -> bool;
+
+/**
+ * Checks filter evaluation against integral values.
+ * @tparam IntegralValueType
+ * @param filter_to_test
+ * @param filter_operand_literal_type
+ * @return Whether all evaluations match their expected results.
+ */
+template <typename IntegralValueType>
+requires std::same_as<IntegralValueType, value_int_t>
+         || std::same_as<IntegralValueType, value_float_t>
+[[nodiscard]] auto check_filter_evaluation_against_integral_values(
+        FilterExpr const* filter_to_test,
+        LiteralType filter_operand_literal_type
+) -> bool;
+
+/**
+ * Checks filter evaluation against bool values.
+ * @param filter_to_test
+ * @param filter_operand_literal_type
+ * @return Whether all evaluations match their expected results.
+ */
+[[nodiscard]] auto check_filter_evaluation_against_bool_values(
+        FilterExpr const* filter_to_test,
+        LiteralType filter_operand_literal_type
+) -> bool;
+
+/**
+ * Checks filter evaluation against and variable strings.
+ * @param filter_to_test
+ * @param filter_operand_literal_type
+ * @return Whether all evaluations match their expected results.
+ */
+[[nodiscard]] auto check_filter_evaluation_against_var_strings(
+        FilterExpr const* filter_to_test,
+        LiteralType filter_operand_literal_type
+) -> bool;
+
+/**
+ * Checks filter evaluation against and encoded text ASTs (Clp strings).
+ * @tparam encoded_variable_t
+ * @param filter_to_test
+ * @param filter_operand_literal_type
+ * @return Whether the filter evaluation results match expectation.
+ */
+template <typename encoded_variable_t>
+requires std::is_same_v<encoded_variable_t, eight_byte_encoded_variable_t>
+         || std::is_same_v<encoded_variable_t, four_byte_encoded_variable_t>
+[[nodiscard]] auto check_filter_evaluation_against_encoded_text_asts(
+        FilterExpr const* filter_to_test,
+        LiteralType filter_operand_literal_type
+) -> bool;
+
+/**
+ * Checks filter evaluation against all supported values.
+ * @param filter_to_test
+ * @param filter_operand_literal_type
+ * @return Whether all evaluations match their expected results.
+ */
+[[nodiscard]] auto
+check_filter_evaluation(FilterExpr const* filter_to_test, LiteralType filter_operand_literal_type)
+        -> bool;
+
+template <typename encoded_variable_t>
+requires std::is_same_v<encoded_variable_t, eight_byte_encoded_variable_t>
+         || std::is_same_v<encoded_variable_t, four_byte_encoded_variable_t>
+auto get_encoded_text_ast(std::string_view text) -> clp::ir::EncodedTextAst<encoded_variable_t> {
+    std::string logtype;
+    std::vector<encoded_variable_t> encoded_vars;
+    std::vector<int32_t> dict_var_bounds;
+    REQUIRE(clp::ffi::encode_message(text, logtype, encoded_vars, dict_var_bounds));
+    REQUIRE(((dict_var_bounds.size() % 2) == 0));
+
+    std::vector<std::string> dict_vars;
+    for (size_t i{0}; i < dict_var_bounds.size(); i += 2) {
+        auto const begin_pos{static_cast<size_t>(dict_var_bounds[i])};
+        auto const end_pos{static_cast<size_t>(dict_var_bounds[i + 1])};
+        dict_vars.emplace_back(text.cbegin() + begin_pos, text.cbegin() + end_pos);
+    }
+
+    return clp::ir::EncodedTextAst<encoded_variable_t>{logtype, dict_vars, encoded_vars};
+}
path: components/core/src/clp/ffi/ir_stream/search/test/test_utilis.cpp
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>❓ Verification inconclusive</summary>

**Add bounds-checking when slicing `text` for dictionary variables**  

`begin_pos` and `end_pos` originate from `dict_var_bounds`, an artefact produced by `encode_message`.  
Although the preceding `REQUIRE(((dict_var_bounds.size() % 2) == 0))` guarantees pairs, it does **not** guarantee that:

* `begin_pos < end_pos`
* `end_pos <= text.size()`

If either invariant is violated we invoke UB when constructing `std::string(text.cbegin() + …)`.  
A defensive check makes the test more robust to future changes in the encoder.

```diff
-        auto const begin_pos{static_cast<size_t>(dict_var_bounds[i])};
-        auto const end_pos{static_cast<size_t>(dict_var_bounds[i + 1])};
+        auto const begin_pos{static_cast<size_t>(dict_var_bounds[i])};
+        auto const end_pos{static_cast<size_t>(dict_var_bounds[i + 1])};
+        REQUIRE(begin_pos <= end_pos);               // invariant 1
+        REQUIRE(end_pos <= text.size());             // invariant 2
```

---



</details>

**Add explicit bounds checks when extracting dictionary variables**

To make the test more robust against future changes in `encode_message`, assert the two key invariants before slicing `text`:

• In `components/core/src/clp/ffi/ir_stream/search/test/test_utilis.cpp` inside `get_encoded_text_ast(...)`  
  Add checks immediately after computing `begin_pos`/`end_pos`:  
```diff
-        auto const begin_pos{static_cast<size_t>(dict_var_bounds[i])};
-        auto const end_pos{static_cast<size_t>(dict_var_bounds[i + 1])};
+        auto const begin_pos{static_cast<size_t>(dict_var_bounds[i])};
+        auto const end_pos{static_cast<size_t>(dict_var_bounds[i + 1])};
+        REQUIRE(begin_pos <= end_pos);      // ensure we don’t underflow
+        REQUIRE(end_pos <= text.size());    // ensure we stay within bounds
```

These `REQUIRE` statements will fail fast if `dict_var_bounds` ever produces out-of-order or out-of-range indices.  


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    for (size_t i{0}; i < dict_var_bounds.size(); i += 2) {
        auto const begin_pos{static_cast<size_t>(dict_var_bounds[i])};
        auto const end_pos{static_cast<size_t>(dict_var_bounds[i + 1])};
        REQUIRE(begin_pos <= end_pos);      // ensure we don’t underflow
        REQUIRE(end_pos <= text.size());    // ensure we stay within bounds
        dict_vars.emplace_back(text.cbegin() + begin_pos, text.cbegin() + end_pos);
    }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-01T18:18:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,48 @@
+#include <catch2/catch.hpp>
+
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+TEST_CASE("WIP", "[ffi]") {
+    /*
+     * <0:root:Obj>
+     *      |
+     *      |------------> <1:a:Obj>
+     *      |                  |
+     *      |--> <2:b:Int>     |--> <3:b:Obj>
+     *      |                  |        |
+     *      |--> <12:a:Int>    |        |------------> <4:c:Obj>
+     *                         |        |                  |
+     *                         |        |--> <5:d:Str>     |--> <7:a:UnstructuredArray>
+     *                         |        |                  |
+     *                         |        |--> <6:d:Bool>    |--> <8:d:Str>
+     *                         |        |                  |
+     *                         |        |--> <10:e:Obj>    |--> <9:d:Float>
+     *                         |                           |
+     *                         |--> <13:b:Bool>            |--> <11:f:Obj>
+     */
+    auto const auto_gen_keys_schema_tree{std::make_shared<SchemaTree>()};
+    auto const user_gen_keys_schema_tree{std::make_shared<SchemaTree>()};
+    std::vector<SchemaTree::NodeLocator> const locators{
+            {SchemaTree::cRootId, "a", SchemaTree::Node::Type::Obj},
+            {SchemaTree::cRootId, "b", SchemaTree::Node::Type::Int},
+            {1, "b", SchemaTree::Node::Type::Obj},
+            {3, "c", SchemaTree::Node::Type::Obj},
+            {3, "d", SchemaTree::Node::Type::Str},
+            {3, "d", SchemaTree::Node::Type::Bool},
+            {4, "a", SchemaTree::Node::Type::UnstructuredArray},
+            {4, "d", SchemaTree::Node::Type::Str},
+            {4, "d", SchemaTree::Node::Type::Float},
+            {3, "e", SchemaTree::Node::Type::Obj},
+            {4, "f", SchemaTree::Node::Type::Obj},
+            {SchemaTree::cRootId, "a", SchemaTree::Node::Type::Int},
+            {1, "b", SchemaTree::Node::Type::Bool}
+    };
+    for (auto const& locator : locators) {
+        REQUIRE_NOTHROW(auto_gen_keys_schema_tree->insert_node(locator));
+        REQUIRE_NOTHROW(user_gen_keys_schema_tree->insert_node(locator));
+    }
+
+    INFO(get_schema_tree_node_queries(*auto_gen_keys_schema_tree));
+}
path: components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Test doesn't validate QueryHandlerImpl functionality.**

This test focuses on schema tree construction but doesn't test the QueryHandlerImpl functionality directly. According to the PR objectives, you're implementing the QueryHandler component, so consider adding specific tests for the QueryHandlerImpl methods, particularly `update_partially_resolved_columns`.

---

_🛠️ Refactor suggestion_

**Test lacks assertions beyond insertion checks.**

The test constructs schema trees and inserts nodes, but doesn't verify the structure beyond checking that insertions don't throw exceptions. Consider adding assertions to validate the expected schema tree structure and node relationships.


For example, add assertions to verify:
- Node counts
- Parent-child relationships
- Node types at specific positions

```diff
    INFO(get_schema_tree_node_queries(*auto_gen_keys_schema_tree));
+   
+   // Verify the number of nodes in the schema tree
+   REQUIRE(auto_gen_keys_schema_tree->get_node_count() == locators.size() + 1); // +1 for root
+   
+   // Verify specific node relationships
+   auto& node3 = auto_gen_keys_schema_tree->get_node(3);
+   REQUIRE(node3.get_children_ids().size() == 4); // Node 3 should have 4 children
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
TEST_CASE("WIP", "[ffi]") {
    /*
     * <0:root:Obj>
     *      |
     *      |------------> <1:a:Obj>
     *      |                  |
     *      |--> <2:b:Int>     |--> <3:b:Obj>
     *      |                  |        |
     *      |--> <12:a:Int>    |        |------------> <4:c:Obj>
     *                         |        |                  |
     *                         |        |--> <5:d:Str>     |--> <7:a:UnstructuredArray>
     *                         |        |                  |
     *                         |        |--> <6:d:Bool>    |--> <8:d:Str>
     *                         |        |                  |
     *                         |        |--> <10:e:Obj>    |--> <9:d:Float>
     *                         |                           |
     *                         |--> <13:b:Bool>            |--> <11:f:Obj>
     */
    auto const auto_gen_keys_schema_tree{std::make_shared<SchemaTree>()};
    auto const user_gen_keys_schema_tree{std::make_shared<SchemaTree>()};
    std::vector<SchemaTree::NodeLocator> const locators{
            {SchemaTree::cRootId, "a", SchemaTree::Node::Type::Obj},
            {SchemaTree::cRootId, "b", SchemaTree::Node::Type::Int},
            {1, "b", SchemaTree::Node::Type::Obj},
            {3, "c", SchemaTree::Node::Type::Obj},
            {3, "d", SchemaTree::Node::Type::Str},
            {3, "d", SchemaTree::Node::Type::Bool},
            {4, "a", SchemaTree::Node::Type::UnstructuredArray},
            {4, "d", SchemaTree::Node::Type::Str},
            {4, "d", SchemaTree::Node::Type::Float},
            {3, "e", SchemaTree::Node::Type::Obj},
            {4, "f", SchemaTree::Node::Type::Obj},
            {SchemaTree::cRootId, "a", SchemaTree::Node::Type::Int},
            {1, "b", SchemaTree::Node::Type::Bool}
    };
    for (auto const& locator : locators) {
        REQUIRE_NOTHROW(auto_gen_keys_schema_tree->insert_node(locator));
        REQUIRE_NOTHROW(user_gen_keys_schema_tree->insert_node(locator));
    }

    INFO(get_schema_tree_node_queries(*auto_gen_keys_schema_tree));

    // Verify the number of nodes in the schema tree
    REQUIRE(auto_gen_keys_schema_tree->get_node_count() == locators.size() + 1); // +1 for root

    // Verify specific node relationships
    auto& node3 = auto_gen_keys_schema_tree->get_node(3);
    REQUIRE(node3.get_children_ids().size() == 4); // Node 3 should have 4 children
}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit b8d69b5
created_at: 2025-05-01T20:53:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,85 @@
+#include "utils.hpp"
+
+#include <bitset>
+#include <map>
+#include <ostream>
+#include <string>
+#include <vector>
+
+#include <fmt/format.h>
+
+#include "../../../SchemaTree.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+auto PossibleMatches::serialize() const -> std::string {
+    return fmt::format(
+            "Types: {}; Node IDs: {{}}",
+            std::bitset<sizeof(m_possible_types)>(m_possible_types).to_string(),
+            fmt::join(m_possible_node_ids, ",")
path: components/core/src/clp/ffi/ir_stream/search/test/utils.cpp
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Issue with format string in PossibleMatches::serialize.**

The format string for node IDs appears problematic. It uses double curly braces which escape to a single curly brace, but doesn't include a format specifier for the joined IDs.

```diff
    return fmt::format(
-           "Types: {}; Node IDs: {{}}",
+           "Types: {}; Node IDs: {}",
            std::bitset<sizeof(m_possible_types)>(m_possible_types).to_string(),
            fmt::join(m_possible_node_ids, ",")
    );
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    return fmt::format(
           "Types: {}; Node IDs: {}",
           std::bitset<sizeof(m_possible_types)>(m_possible_types).to_string(),
           fmt::join(m_possible_node_ids, ",")
    );
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit b8d69b5
created_at: 2025-05-01T20:53:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -457,7 +457,12 @@ set(SOURCE_FILES_unitTest
         src/clp/ffi/ir_stream/search/ErrorCode.hpp
         src/clp/ffi/ir_stream/search/NewProjectedSchemaTreeNodeCallbackReq.hpp
         src/clp/ffi/ir_stream/search/QueryHandler.hpp
-        src/clp/ffi/ir_stream/search/test/test_utils.cpp
+        src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
+        src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
+        src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp
+        src/clp/ffi/ir_stream/search/test/test_utilis.cpp
+        src/clp/ffi/ir_stream/search/test/utils.cpp
+        src/clp/ffi/ir_stream/search/test/utils.hpp
         src/clp/ffi/ir_stream/search/utils.cpp
         src/clp/ffi/ir_stream/search/utils.hpp
path: components/core/CMakeLists.txt
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Consider dropping headers from `SOURCE_FILES_unitTest`**

`.hpp` files (`QueryHandlerImpl.hpp`, `utils.hpp`, etc.) do not need to be explicitly enumerated in `target_sources`; they are picked up by the compiler automatically via `#include`s.  Keeping only `.cpp` files reduces clutter and prevents accidental duplication of translation units when a header is later renamed and its old path forgotten in CMake.

This is optional, but helps keep the build script maintainable.

---

_💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Possible typo: `test_utilis.cpp` may break the build**

`test_utilis.cpp` is listed here but the conventional spelling elsewhere in the tree is `test_utils.cpp` (with a second “t”).  
If the physical filename uses the correct spelling while CMake refers to the miss-spelled path, the compiler will fail with a *file not found* error; if the file on disk is actually miss-spelled, readers will be confused and IDE auto-completion will not match the helper header `utils.hpp`.

```diff
-        src/clp/ffi/ir_stream/search/test/test_utilis.cpp
+        src/clp/ffi/ir_stream/search/test/test_utils.cpp
```

Please double-check the actual filename on disk and keep the spelling consistent throughout the project.

---

🏁 Script executed:

```shell
#!/bin/bash
# List all files in the test directory to verify spelling
find src/clp/ffi/ir_stream/search/test -maxdepth 1 -type f -printf "%f\n"
```

Length of output: 144

---

🏁 Script executed:

```shell
#!/bin/bash
# Locate the test directory under ir_stream/search
echo "Search directories ending with 'search/test':"
find . -type d | grep -E "search/test$" || echo "No matching test directory found"

# List all test files matching potential typo
echo "Files named test_utilis.cpp or test_utils.cpp:"
find . -type f \( -iname "test_utilis.cpp" -o -iname "test_utils.cpp" \) -print || echo "No such files found"
```

Length of output: 451

---



</details>

**Fix typo in test file name**

The file on disk is named  
  components/core/src/clp/ffi/ir_stream/search/test/test_utilis.cpp  
but it should be spelled `test_utils.cpp` to match the rest of the project and avoid build/IDE issues. Please rename the file and update the CMakeLists entry:

• File to rename:  
  components/core/src/clp/ffi/ir_stream/search/test/test_utilis.cpp → components/core/src/clp/ffi/ir_stream/search/test/test_utils.cpp

• In components/core/CMakeLists.txt (lines 460–467), update:
```diff
-        src/clp/ffi/ir_stream/search/test/test_utilis.cpp
+        src/clp/ffi/ir_stream/search/test/test_utils.cpp
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
        src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
        src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
        src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp
        src/clp/ffi/ir_stream/search/test/test_utils.cpp
        src/clp/ffi/ir_stream/search/test/utils.cpp
        src/clp/ffi/ir_stream/search/test/utils.hpp
        src/clp/ffi/ir_stream/search/utils.cpp
        src/clp/ffi/ir_stream/search/utils.hpp
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commits 900ccf9 to 350552c
created_at: 2025-05-02T20:49:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,84 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+
+#include <map>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../utils.hpp"
+
path: components/core/src/clp/ffi/ir_stream/search/test/utils.hpp
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Overly deep relative include paths reduce portability**

The header uses five `..` segments to reach `clp_s/search/ast/Literal.hpp`.  Such paths are fragile if directory names change.

Consider adding `${PROJECT_SOURCE_DIR}/src` (or an equivalent include directory) in the CMake target for unit tests and then replacing:

```cpp
#include "../../../../../clp_s/search/ast/Literal.hpp"
```

with:

```cpp
#include "clp_s/search/ast/Literal.hpp"
```

This keeps the include list readable and future-proof.

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T20:49:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,84 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+
+#include <map>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+class ColumnQueryPossibleMatches {
+public:
+    ColumnQueryPossibleMatches(std::shared_ptr<SchemaTree> schema_tree)
+            : m_schema_tree{std::move(schema_tree)} {}
+
+    [[nodiscard]] auto get_matchable_types() const -> clp_s::search::ast::LiteralTypeBitmask {
+        return m_matchable_types;
+    }
+
+    [[nodiscard]] auto get_matchable_node_ids() const -> std::set<SchemaTree::Node::id_t> const& {
+        return m_matchable_node_ids;
+    }
+
+    [[nodiscard]] auto get_matchable_node_ids_from_literal_type(
+            clp_s::search::ast::LiteralType type
+    ) const -> std::vector<SchemaTree::Node::id_t>;
+
+    [[nodiscard]] auto get_matchable_node_ids_from_schema_tree_type(
+            SchemaTree::Node::Type type
+    ) const -> std::vector<SchemaTree::Node::id_t>;
+
+    [[nodiscard]] auto get_projectable_node_ids() const -> std::vector<SchemaTree::Node::id_t>;
+
+    auto set_matchable_node(SchemaTree::Node::id_t node_id, SchemaTree::Node::Type type) -> void {
+        m_matchable_node_ids.emplace(node_id);
+        m_matchable_types |= schema_tree_node_type_to_literal_types(type);
+    }
+
path: components/core/src/clp/ffi/ir_stream/search/test/utils.hpp
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**`set_matchable_node` could be marked `noexcept` and return `bool`**

The method performs only `emplace` and bitwise-OR; it cannot throw under normal circumstances.  
Returning a `bool` that indicates whether the node was newly inserted helps callers avoid duplicate work:

```diff
-auto set_matchable_node(SchemaTree::Node::id_t node_id, SchemaTree::Node::Type type) -> void {
-    m_matchable_node_ids.emplace(node_id);
-    m_matchable_types |= schema_tree_node_type_to_literal_types(type);
-}
+[[nodiscard]] auto set_matchable_node(SchemaTree::Node::id_t node_id,
+                                     SchemaTree::Node::Type type) noexcept -> bool {
+    auto [it, inserted] = m_matchable_node_ids.emplace(node_id);
+    if (inserted) {
+        m_matchable_types |= schema_tree_node_type_to_literal_types(type);
+    }
+    return inserted;
+}
```

This is a minor enhancement, but it tightens the contract and can save needless work in tight loops.



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    [[nodiscard]] auto get_matchable_types() const -> clp_s::search::ast::LiteralTypeBitmask {
        return m_matchable_types;
    }

    [[nodiscard]] auto get_matchable_node_ids() const -> std::set<SchemaTree::Node::id_t> const& {
        return m_matchable_node_ids;
    }

    [[nodiscard]] auto get_matchable_node_ids_from_literal_type(
            clp_s::search::ast::LiteralType type
    ) const -> std::vector<SchemaTree::Node::id_t>;

    [[nodiscard]] auto get_matchable_node_ids_from_schema_tree_type(
            SchemaTree::Node::Type type
    ) const -> std::vector<SchemaTree::Node::id_t>;

    [[nodiscard]] auto get_projectable_node_ids() const -> std::vector<SchemaTree::Node::id_t>;

    [[nodiscard]] auto set_matchable_node(SchemaTree::Node::id_t node_id,
                                          SchemaTree::Node::Type type) noexcept -> bool {
        auto [it, inserted] = m_matchable_node_ids.emplace(node_id);
        if (inserted) {
            m_matchable_types |= schema_tree_node_type_to_literal_types(type);
        }
        return inserted;
    }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T20:49:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,161 @@
+#include "utils.hpp"
+
+#include <bitset>
+#include <map>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../SchemaTree.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::search::ast::LiteralType;
+}  // namespace
+
+auto ColumnQueryPossibleMatches::get_matchable_node_ids_from_literal_type(LiteralType type) const
+        -> std::vector<SchemaTree::Node::id_t> {
+    if ((type & m_matchable_types) == 0) {
+        return {};
+    }
+
+    switch (type) {
+        case LiteralType::IntegerT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Int);
+        case LiteralType::FloatT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Float);
+        case LiteralType::BooleanT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Bool);
+        case LiteralType::VarStringT:
+        case LiteralType::ClpStringT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Str);
+        default:
+            return {};
+    }
+}
+
+auto ColumnQueryPossibleMatches::get_matchable_node_ids_from_schema_tree_type(
+        SchemaTree::Node::Type type
+) const -> std::vector<SchemaTree::Node::id_t> {
+    std::vector<SchemaTree::Node::id_t> node_ids;
+    for (auto const node_id : m_matchable_node_ids) {
+        if (m_schema_tree->get_node(node_id).get_type() == type) {
+            node_ids.emplace_back(node_id);
+        }
+    }
+    return node_ids;
+}
+
+auto ColumnQueryPossibleMatches::get_projectable_node_ids() const
+        -> std::vector<SchemaTree::Node::id_t> {
+    std::vector<SchemaTree::Node::id_t> non_object_node_ids;
+    for (auto const node_id : m_matchable_node_ids) {
+        if (m_schema_tree->get_node(node_id).get_type() == SchemaTree::Node::Type::Obj) {
+            non_object_node_ids.emplace_back(node_id);
+        }
+    }
+    return non_object_node_ids;
path: components/core/src/clp/ffi/ir_stream/search/test/utils.cpp
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**`get_projectable_node_ids` pushes *object* nodes instead of *non-object* nodes**

The function name and the local variable (`non_object_node_ids`) imply that only *non-object* nodes should be collected, yet the condition currently does the opposite and gathers `Type::Obj` nodes.

```diff
-        if (m_schema_tree->get_node(node_id).get_type() == SchemaTree::Node::Type::Obj) {
+        if (m_schema_tree->get_node(node_id).get_type() != SchemaTree::Node::Type::Obj) {
```

Returning object IDs here will cause projections to be generated for container nodes instead of leaf values, breaking later filter / projection logic.  



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
auto ColumnQueryPossibleMatches::get_projectable_node_ids() const
        -> std::vector<SchemaTree::Node::id_t> {
    std::vector<SchemaTree::Node::id_t> non_object_node_ids;
    for (auto const node_id : m_matchable_node_ids) {
        if (m_schema_tree->get_node(node_id).get_type() != SchemaTree::Node::Type::Obj) {
            non_object_node_ids.emplace_back(node_id);
        }
    }
    return non_object_node_ids;
}
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Cppcheck (2.10-2)</summary>

[style] 56-56: The function 'get_projectable_node_ids' is never used.

(unusedFunction)

</details>

</details>

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T20:49:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,161 @@
+#include "utils.hpp"
+
+#include <bitset>
+#include <map>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../SchemaTree.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::search::ast::LiteralType;
+}  // namespace
+
+auto ColumnQueryPossibleMatches::get_matchable_node_ids_from_literal_type(LiteralType type) const
+        -> std::vector<SchemaTree::Node::id_t> {
+    if ((type & m_matchable_types) == 0) {
+        return {};
+    }
+
+    switch (type) {
+        case LiteralType::IntegerT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Int);
+        case LiteralType::FloatT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Float);
+        case LiteralType::BooleanT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Bool);
+        case LiteralType::VarStringT:
+        case LiteralType::ClpStringT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Str);
+        default:
+            return {};
+    }
+}
+
+auto ColumnQueryPossibleMatches::get_matchable_node_ids_from_schema_tree_type(
+        SchemaTree::Node::Type type
+) const -> std::vector<SchemaTree::Node::id_t> {
+    std::vector<SchemaTree::Node::id_t> node_ids;
+    for (auto const node_id : m_matchable_node_ids) {
+        if (m_schema_tree->get_node(node_id).get_type() == type) {
+            node_ids.emplace_back(node_id);
+        }
+    }
+    return node_ids;
+}
+
+auto ColumnQueryPossibleMatches::get_projectable_node_ids() const
+        -> std::vector<SchemaTree::Node::id_t> {
+    std::vector<SchemaTree::Node::id_t> non_object_node_ids;
+    for (auto const node_id : m_matchable_node_ids) {
+        if (m_schema_tree->get_node(node_id).get_type() == SchemaTree::Node::Type::Obj) {
+            non_object_node_ids.emplace_back(node_id);
+        }
+    }
+    return non_object_node_ids;
+}
+
+auto ColumnQueryPossibleMatches::serialize() const -> std::string {
+    return fmt::format(
+            "Types: {}; Node IDs: {{{}}}",
+            std::bitset<sizeof(m_matchable_types) * 8>(m_matchable_types).to_string(),
+            fmt::join(m_matchable_node_ids, ",")
+    );
+}
path: components/core/src/clp/ffi/ir_stream/search/test/utils.cpp
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Double-check the `fmt::format` brace escaping**

`fmt` requires balanced braces – literal braces are escaped with `{{` / `}}`.  
If compilation fails with an “unmatched ‘}’” error, replace the format string with the simpler variant below which also avoids unnecessary escaping:

```diff
-    return fmt::format(
-            "Types: {}; Node IDs: {{{}}}",
-            std::bitset<sizeof(m_matchable_types) * 8>(m_matchable_types).to_string(),
-            fmt::join(m_matchable_node_ids, ",")
-    );
+    return fmt::format(
+            "Types: {}; Node IDs: {}",
+            std::bitset<sizeof(m_matchable_types) * 8>(m_matchable_types).to_string(),
+            fmt::format("{{{}}}", fmt::join(m_matchable_node_ids, ","))
+    );
```
This guarantees correct escaping and keeps the braces in the output.



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
auto ColumnQueryPossibleMatches::serialize() const -> std::string {
    return fmt::format(
            "Types: {}; Node IDs: {}",
            std::bitset<sizeof(m_matchable_types) * 8>(m_matchable_types).to_string(),
            fmt::format("{{{}}}", fmt::join(m_matchable_node_ids, ","))
    );
}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T20:49:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,349 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param colum_descriptor
+         * @return A result containing the constructed token iterator on success, or an error code
+         * indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the column doesn't have any
+         *   token.
+         */
+        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* colum_descriptor)
+                -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            auto const token_begin_it{colum_descriptor->descriptor_begin()};
+            if (colum_descriptor->descriptor_end() == token_begin_it) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound};
+            }
+            return ColumnDescriptorTokenIterator{colum_descriptor, token_begin_it};
+        }
+
+        // Methods
+        /**
+         * @return Whether this is the last token of the underlying column descriptor.
+         */
+        [[nodiscard]] auto is_last() const -> bool {
+            return m_column_descriptor->descriptor_end() == m_next_token_it;
+        }
+
+        /**
+         * @return A result containing a newly constructor iterator pointing to the next token on
+         * success, or an error code indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the current token is already
+         *   the last one.
+         */
+        [[nodiscard]] auto next() const -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            if (is_last()) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound};
+            }
+            return ColumnDescriptorTokenIterator{m_column_descriptor, m_next_token_it};
+        }
+
+        [[nodiscard]] auto is_wildcard() const -> bool { return m_curr_token_it->wildcard(); }
+
+        [[nodiscard]] auto get_token() const -> std::string_view {
+            return m_curr_token_it->get_token();
+        }
+
+        [[nodiscard]] auto get_column_descriptor() const -> clp_s::search::ast::ColumnDescriptor* {
+            return m_column_descriptor;
+        }
+
+        /**
+         * @param type
+         * @return Whether the underlying column can match the given schema-tree node type.
+         */
+        [[nodiscard]] auto match(SchemaTree::Node::Type type) const -> bool {
+            return m_column_descriptor->matches_any(schema_tree_node_type_to_literal_types(type));
+        }
+
+    private:
+        // Constructor
+        ColumnDescriptorTokenIterator(
+                clp_s::search::ast::ColumnDescriptor* colum_descriptor,
+                clp_s::search::ast::DescriptorList::iterator curr_token_it
+        )
+                : m_column_descriptor{colum_descriptor},
+                  m_curr_token_it{curr_token_it},
+                  m_next_token_it{curr_token_it + 1} {}
+
+        clp_s::search::ast::ColumnDescriptor* m_column_descriptor;
+        clp_s::search::ast::DescriptorList::iterator m_curr_token_it;
+        clp_s::search::ast::DescriptorList::iterator m_next_token_it;
+    };
+
+    using ProjectionMap = std::unordered_map<clp_s::search::ast::ColumnDescriptor*, std::string>;
+
+    using PartialResolutionMap = std::
+            unordered_map<SchemaTree::Node::id_t, std::vector<ColumnDescriptorTokenIterator>>;
+
+    // Factory function
+    /**
+     * @param query The search query.
+     * @param projections The columns to project.
+     * @param case_sensitive_match Whether to use case-sensitive match for string comparison.
+     * @return A result containing the newly constructed `QueryHandler` on success, or an error code
+     * indicating the failure:
+     * - Forwards `preprocess_query`'s return values.
+     * - Forwards `create_projected_columns_and_projection_map`'s return values.
+     * - Forwards `create_initial_partial_resolutions`'s return values.
+     */
+    [[nodiscard]] static auto create(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            std::vector<std::pair<std::string, clp_s::search::ast::LiteralTypeBitmask>> const&
+                    projections,
+            bool case_sensitive_match
+    ) -> outcome_v2::std_result<QueryHandlerImpl>;
+
+    // Delete copy constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl const&) = delete;
+    auto operator=(QueryHandlerImpl const&) -> QueryHandlerImpl& = delete;
+
+    // Default move constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl&&) = default;
+    auto operator=(QueryHandlerImpl&&) -> QueryHandlerImpl& = default;
+
+    // Destructor
+    ~QueryHandlerImpl() = default;
+
+    /**
+     * Implementation of `QueryHandler::evaluate_node_id_value_pairs`.
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - TODO
+     */
+    [[nodiscard]] auto evaluate_node_id_value_pairs(
+            KeyValuePairLogEvent::NodeIdValuePairs const& auto_gen_node_id_value_pairs,
+            KeyValuePairLogEvent::NodeIdValuePairs const& user_gen_node_id_value_pairs
+    ) -> outcome_v2::std_result<AstEvaluationResult>;
+
+    /**
+     * Implementation of `QueryHandler::update_partially_resolved_columns` with new projected
+     * schema-tree node callback given as a template parameter.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @param new_projected_schema_tree_node_callback
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `handle_column_resolution_on_leaf_node`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto update_partially_resolved_columns(
+            bool is_auto_generated,
+            SchemaTree::NodeLocator const& node_locator,
+            SchemaTree::Node::id_t node_id,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    [[nodiscard]] auto get_resolved_column_to_schema_tree_node_ids() const -> std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>> {
+        return m_resolved_column_to_schema_tree_node_ids;
+    }
+
+private:
+    // Constructor
+    QueryHandlerImpl(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            PartialResolutionMap auto_gen_namespace_partial_resolutions,
+            PartialResolutionMap user_gen_namespace_partial_resolutions,
+            std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> projected_columns,
+            ProjectionMap projected_column_to_original_key,
+            bool case_sensitive_match
+    )
+            : m_query{std::move(query)},
+              m_auto_gen_namespace_partial_resolutions{
+                      std::move(auto_gen_namespace_partial_resolutions)
+              },
+              m_user_gen_namespace_partial_resolutions{
+                      std::move(user_gen_namespace_partial_resolutions)
+              },
+              m_projected_columns{std::move(projected_columns)},
+              m_projected_column_to_original_key{std::move(projected_column_to_original_key)},
+              m_case_sensitive_search{case_sensitive_match} {}
+
+    // Methods
+    /**
+     * Handles column resolution on the given schema-tree leaf node and the token in the column.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param is_auto_generated
+     * @param leaf_node_id
+     * @param leaf_node_locator
+     * @param token_it The current resolved token in the column.
+     * @param new_projected_schema_tree_node_callback
+     * @return A void result on success, or an error code indicating the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `new_projected_schema_tree_node_callback`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto handle_column_resolution_on_leaf_node(
+            bool is_auto_generated,
+            SchemaTree::Node::id_t leaf_node_id,
+            SchemaTree::NodeLocator const& leaf_node_locator,
+            ColumnDescriptorTokenIterator const& token_it,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    // Variables
+    std::shared_ptr<clp_s::search::ast::Expression> m_query;
+    PartialResolutionMap m_auto_gen_namespace_partial_resolutions;
+    PartialResolutionMap m_user_gen_namespace_partial_resolutions;
+    std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>>
+            m_resolved_column_to_schema_tree_node_ids;
+    std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> m_projected_columns;
+    ProjectionMap m_projected_column_to_original_key;
+    bool m_case_sensitive_search;
+};
+
+template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+auto QueryHandlerImpl::update_partially_resolved_columns(
+        bool is_auto_generated,
+        SchemaTree::NodeLocator const& node_locator,
+        SchemaTree::Node::id_t node_id,
+        NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+) -> outcome_v2::std_result<void> {
+    auto const parent_node_id{node_locator.get_parent_id()};
+    auto& partial_resolutions_to_update{
+            is_auto_generated ? m_auto_gen_namespace_partial_resolutions
+                              : m_user_gen_namespace_partial_resolutions
+    };
+    if (false == partial_resolutions_to_update.contains(parent_node_id)) {
+        return outcome_v2::success();
+    }
+
+    for (auto const& token_it : partial_resolutions_to_update.at(parent_node_id)) {
+        auto const next_token_it_result{token_it.next()};
+        if (SchemaTree::Node::Type::Obj == node_locator.get_type()) {
+            // Handle schema-tree non-leaf nodes.
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

_⚠️ Potential issue_

**Iterator invalidation and unused result in partial-resolution update**

1. `auto const next_token_it_result{token_it.next()};`  
   - The result is never inspected or used, yet calling `next()` on a *last* token will return an error that is silently discarded – undefined behaviour for `outcome_v2`.
2. While iterating over `partial_resolutions_to_update.at(parent_node_id)` the map is mutated with `try_emplace`, which may trigger a re-hash and invalidate the **vector reference** being iterated.

A safe & simpler pattern is to copy the vector first and only call `next()` when you really need it:

```diff
-    for (auto const& token_it : partial_resolutions_to_update.at(parent_node_id)) {
-        auto const next_token_it_result{token_it.next()};
+    auto tokens = partial_resolutions_to_update.at(parent_node_id);   // copy – no invalidation
+    for (auto const& token_it : tokens) {
```

Remove the unused `next_token_it_result` line altogether; subsequent code already calls `token_it.next()` conditionally and handles its result correctly.

This change eliminates a latent use-after-iterator bug and avoids swallowing potential `ColumnDescriptorTokenIteratorOutOfBound` errors.  




<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    auto& partial_resolutions_to_update{
            is_auto_generated ? m_auto_gen_namespace_partial_resolutions
                              : m_user_gen_namespace_partial_resolutions
    };
    if (false == partial_resolutions_to_update.contains(parent_node_id)) {
        return outcome_v2::success();
    }

    auto tokens = partial_resolutions_to_update.at(parent_node_id);   // copy – no invalidation
    for (auto const& token_it : tokens) {
        if (SchemaTree::Node::Type::Obj == node_locator.get_type()) {
            // Handle schema-tree non-leaf nodes.
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commits 0166fb8 to 141242c
created_at: 2025-05-02T20:49:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,419 @@
+#include <map>
+#include <memory>
+#include <set>
+#include <sstream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <catch2/catch.hpp>
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/archive_constants.hpp"
+#include "../../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../../clp_s/search/kql/kql.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../../../Value.hpp"
+#include "../QueryHandlerImpl.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::search::ast::LiteralTypeBitmask;
+
+constexpr std::string_view cRefTestStr{"*test*"};
+constexpr value_int_t cRefTestInt{0};
+constexpr value_float_t cRefTestFloat{0.0};
+constexpr value_bool_t cRefTestBool{false};
+
+/**
+ * Generates all the matchable KQL expressions based on the column queries and their matchable
+ * types. For each column query `C` matched type `T`, we generate an expression of `C: cRefTestT`,
+ * where `cRefTestT` is any of the constants defined above.
+ * @param is_auto_generated Whether the query is in auto-generated namespace.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - A vector of matchable KQL expressions.
+ * - A map of expected column resolutions.
+ */
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        bool is_auto_generated,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param is_auto_generated Whether the query is in auto-generated namespace.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - Generated projections as pairs, where each pair consists of:
+ *   - A projectable column from `column_query_to_possible_matches` that has no wildcard.
+ *   - All the matchable types of this column.
+ * - A map of expected resolved projections.
+ */
+[[nodiscard]] auto generate_projections(
+        bool is_auto_generated,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+)
+        -> std::pair<
+                std::vector<std::pair<std::string, LiteralTypeBitmask>>,
+                std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param column_resolutions
+ * @return The serialized pairs.
+ */
+[[nodiscard]] auto serialize_column_node_ids_map(
+        std::map<std::string, std::set<SchemaTree::Node::id_t>> const& column_resolutions
+) -> std::string;
+
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        bool is_auto_generated,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>> {
+    std::vector<std::string> matchable_kql_expressions;
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> expected_column_resolutions;
+    for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
+        auto const column_query_with_namespace{fmt::format(
+                "{}{}",
+                is_auto_generated ? clp_s::constants::cAutogenNamespace
+                                  : clp_s::constants::cDefaultNamespace,
+                column_query
+        )};
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Int
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestInt)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Float
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {:.2f}", column_query_with_namespace, cRefTestFloat)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Bool
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestBool)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Str
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestStr)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+    }
+
+    if (expected_column_resolutions.contains("*")) {
+        // NOTE: The current implementation doesn't resolve single wildcard.
+        expected_column_resolutions.erase("*");
+    }
+    return {std::move(matchable_kql_expressions), std::move(expected_column_resolutions)};
+}
+
+auto generate_projections(
+        bool is_auto_generated,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+)
+        -> std::pair<
+                std::vector<std::pair<std::string, LiteralTypeBitmask>>,
+                std::map<std::string, std::set<SchemaTree::Node::id_t>>> {
+    std::vector<std::pair<std::string, LiteralTypeBitmask>> projections;
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> expected_resolved_projections;
+    for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
+        if (column_query.find('*') != std::string::npos) {
+            continue;
+        }
+        auto const projectable_node_ids{possible_matches.get_projectable_node_ids()};
+        if (projections.empty()) {
+            continue;
+        }
path: components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Wrong emptiness check prevents any projection from being added**

`projections.empty()` is tested instead of `projectable_node_ids.empty()`, meaning the very first non-wildcard column is always skipped (because `projections` starts empty). This silently drops valid projections and weakens the test.

```diff
-        if (projections.empty()) {
+        if (projectable_node_ids.empty()) {
```

With the fix, only columns that genuinely lack projectable leaves will be skipped, and the generated expectations will match the implementation.  



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
        if (column_query.find('*') != std::string::npos) {
            continue;
        }
        auto const projectable_node_ids{possible_matches.get_projectable_node_ids()};
        if (projectable_node_ids.empty()) {
            continue;
        }
        // ...
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T20:49:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,324 @@
+#include "QueryHandlerImpl.hpp"
+
+#include <exception>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/archive_constants.hpp"
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/ConvertToExists.hpp"
+#include "../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../clp_s/search/ast/NarrowTypes.hpp"
+#include "../../../../clp_s/search/ast/OrOfAndForm.hpp"
+#include "../../../../clp_s/search/ast/SearchUtils.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::EmptyExpr;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Pre-processes a search query by applying several transformation passes.
+ * @param query
+ * @return A result containing the transformed
+ * - ErrorCodeEnum::QueryExpressionIsNull if `query` is nullptr.
+ * - ErrorCodeEnum::QueryTransformationPassFailed if any of the transformation pass failed.
+ */
+[[nodiscard]] auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>>;
+
+/**
+ * Creates projected columns and column-to-original-key map from the given projections.
+ * @param projections
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - A vector of projected columns.
+ *   - A projected-column-to-original-key map.
+ * - The possible error codes:
+ *   - ErrorCodeEnum::DuplicateProjectedColumn if `projections` contains duplicated keys.
+ *   - ErrorCodeEnum::ColumnTokenizationFailure if failed to tokenize a projection key.
+ *   - ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure if failed to create a column
+ *     descriptor for the projected key.
+ */
+[[nodiscard]] auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>>;
+
+/**
+ * Creates initial partial resolutions for the given query and projections.
+ * @param query
+ * @param projected_column_to_original_key
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - The partial resolution for auto-generated keys.
+ *   - The partial resolution for user-generated keys.
+ * - The possible error codes:
+ *   - Forwards `is_auto_generated`'s return values.
+ *   - Forwards `initialize_partial_resolution_from_search_ast`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::create`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::next`'s return values.
+ */
+[[nodiscard]] auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>>;
+
+/**
+ * Initializes partial resolutions from a search AST.
+ * @param root The root of the search AST.
+ * @param auto_gen_namespace_partial_resolutions Returns initialized auto-gen partial resolutions.
+ * @param user_gen_namespace_partial_resolutions Returns initialized user-gen partial resolutions.
+ * @return A void result on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::AstDynamicCastFailure if failed to dynamically cast an AST node to a target
+ *   type.
+ * - Forwards `is_auto_generated`'s return values.
+ */
+[[nodiscard]] auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void>;
+
+/**
+ * @param key_namespace
+ * @return A result containing a boolean indicating whether `key_namespace` is auto-generated or
+ * user-generated on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::UnsupportedNamespace if the namespace is not supported.
+ */
+[[nodiscard]] auto is_auto_generated(std::string_view key_namespace)
+        -> outcome_v2::std_result<bool>;
+
+auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>> {
+    if (nullptr == query) {
+        return ErrorCode{ErrorCodeEnum::QueryExpressionIsNull};
+    }
+
+    if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(query)) {
+        return query;
+    }
+
+    try {
+        if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        if (query = clp_s::search::ast::NarrowTypes{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        return clp_s::search::ast::ConvertToExists{}.run(query);
+    } catch (std::exception const& ex) {
+        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+    }
+}
+
+auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>> {
+    std::unordered_set<std::string_view> unique_projected_columns;
+    std::vector<std::shared_ptr<ColumnDescriptor>> projected_columns;
+    QueryHandlerImpl::ProjectionMap projected_column_to_original_key;
+
+    for (auto const& [key, types] : projections) {
+        if (unique_projected_columns.contains(key)) {
+            return ErrorCode{ErrorCodeEnum::DuplicateProjectedColumn};
+        }
+        unique_projected_columns.emplace(key);
+
+        std::vector<std::string> descriptor_tokens;
+        std::string descriptor_namespace;
+        if (false
+            == clp_s::search::ast::tokenize_column_descriptor(
+                    key,
+                    descriptor_tokens,
+                    descriptor_namespace
+            ))
+        {
+            return ErrorCode{ErrorCodeEnum::ColumnTokenizationFailure};
+        }
+
+        try {
+            auto column_descriptor{clp_s::search::ast::ColumnDescriptor::create_from_escaped_tokens(
+                    descriptor_tokens,
+                    descriptor_namespace
+            )};
+            column_descriptor->set_matching_types(types);
+            if (column_descriptor->is_unresolved_descriptor()
+                || column_descriptor->get_descriptor_list().empty())
+            {
+                return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+            }
+            projected_column_to_original_key.emplace(column_descriptor.get(), key);
+            projected_columns.emplace_back(std::move(column_descriptor));
+        } catch (std::exception const& e) {
+            return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+        }
+    }
+
+    return {std::move(projected_columns), std::move(projected_column_to_original_key)};
+}
+
+auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>> {
+    QueryHandlerImpl::PartialResolutionMap auto_gen_namespace_partial_resolutions;
+    QueryHandlerImpl::PartialResolutionMap user_gen_namespace_partial_resolutions;
+    for (auto const& [col, key] : projected_column_to_original_key) {
+        bool const is_auto_gen{OUTCOME_TRYX(is_auto_generated(col->get_namespace()))};
+        auto& partial_resolutions{
+                is_auto_gen ? auto_gen_namespace_partial_resolutions
+                            : user_gen_namespace_partial_resolutions
+        };
+        auto [it, inserted] = partial_resolutions.try_emplace(
+                SchemaTree::cRootId,
+                std::vector<QueryHandlerImpl::ColumnDescriptorTokenIterator>{}
+        );
+        it->second.emplace_back(
+                OUTCOME_TRYX(QueryHandlerImpl::ColumnDescriptorTokenIterator::create(col))
+        );
+    }
+
+    OUTCOME_TRYV(initialize_partial_resolution_from_search_ast(
+            query,
+            auto_gen_namespace_partial_resolutions,
+            user_gen_namespace_partial_resolutions
+    ));
+
+    return {std::move(auto_gen_namespace_partial_resolutions),
+            std::move(user_gen_namespace_partial_resolutions)};
+}
+
+auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void> {
+    std::vector<Expression*> ast_dfs_stack;
+    ast_dfs_stack.emplace_back(root.get());
+    while (false == ast_dfs_stack.empty()) {
+        auto* expr{ast_dfs_stack.back()};
+        ast_dfs_stack.pop_back();
+        if (expr->has_only_expression_operands()) {
+            for (auto it{expr->op_begin()}; it != expr->op_end(); ++it) {
+                auto* expr{dynamic_cast<Expression*>(it->get())};
+                if (nullptr == expr) {
+                    return ErrorCode{ErrorCodeEnum::AstDynamicCastFailure};
+                }
+                ast_dfs_stack.emplace_back(expr);
+            }
+            continue;
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Variable shadowing inside DFS loop hampers readability & may mask bugs**  
Re-using the identifier `expr` for both the current node and the dereferenced child obscures which pointer is which:

```cpp
for (auto it{expr->op_begin()}; it != expr->op_end(); ++it) {
    auto* expr{dynamic_cast<Expression*>(it->get())};  // shadows outer `expr`
```

Rename the inner pointer to clarify intent:

```diff
-    auto* expr{dynamic_cast<Expression*>(it->get())};
+    auto* child_expr{dynamic_cast<Expression*>(it->get())};
...
-    ast_dfs_stack.emplace_back(expr);
+    ast_dfs_stack.emplace_back(child_expr);
```

This minor refactor prevents accidental use of the wrong variable and improves maintainability.

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T20:49:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,324 @@
+#include "QueryHandlerImpl.hpp"
+
+#include <exception>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/archive_constants.hpp"
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/ConvertToExists.hpp"
+#include "../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../clp_s/search/ast/NarrowTypes.hpp"
+#include "../../../../clp_s/search/ast/OrOfAndForm.hpp"
+#include "../../../../clp_s/search/ast/SearchUtils.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::EmptyExpr;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Pre-processes a search query by applying several transformation passes.
+ * @param query
+ * @return A result containing the transformed
+ * - ErrorCodeEnum::QueryExpressionIsNull if `query` is nullptr.
+ * - ErrorCodeEnum::QueryTransformationPassFailed if any of the transformation pass failed.
+ */
+[[nodiscard]] auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>>;
+
+/**
+ * Creates projected columns and column-to-original-key map from the given projections.
+ * @param projections
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - A vector of projected columns.
+ *   - A projected-column-to-original-key map.
+ * - The possible error codes:
+ *   - ErrorCodeEnum::DuplicateProjectedColumn if `projections` contains duplicated keys.
+ *   - ErrorCodeEnum::ColumnTokenizationFailure if failed to tokenize a projection key.
+ *   - ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure if failed to create a column
+ *     descriptor for the projected key.
+ */
+[[nodiscard]] auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>>;
+
+/**
+ * Creates initial partial resolutions for the given query and projections.
+ * @param query
+ * @param projected_column_to_original_key
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - The partial resolution for auto-generated keys.
+ *   - The partial resolution for user-generated keys.
+ * - The possible error codes:
+ *   - Forwards `is_auto_generated`'s return values.
+ *   - Forwards `initialize_partial_resolution_from_search_ast`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::create`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::next`'s return values.
+ */
+[[nodiscard]] auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>>;
+
+/**
+ * Initializes partial resolutions from a search AST.
+ * @param root The root of the search AST.
+ * @param auto_gen_namespace_partial_resolutions Returns initialized auto-gen partial resolutions.
+ * @param user_gen_namespace_partial_resolutions Returns initialized user-gen partial resolutions.
+ * @return A void result on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::AstDynamicCastFailure if failed to dynamically cast an AST node to a target
+ *   type.
+ * - Forwards `is_auto_generated`'s return values.
+ */
+[[nodiscard]] auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void>;
+
+/**
+ * @param key_namespace
+ * @return A result containing a boolean indicating whether `key_namespace` is auto-generated or
+ * user-generated on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::UnsupportedNamespace if the namespace is not supported.
+ */
+[[nodiscard]] auto is_auto_generated(std::string_view key_namespace)
+        -> outcome_v2::std_result<bool>;
+
+auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>> {
+    if (nullptr == query) {
+        return ErrorCode{ErrorCodeEnum::QueryExpressionIsNull};
+    }
+
+    if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(query)) {
+        return query;
+    }
+
+    try {
+        if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        if (query = clp_s::search::ast::NarrowTypes{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        return clp_s::search::ast::ConvertToExists{}.run(query);
+    } catch (std::exception const& ex) {
+        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+    }
+}
+
+auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>> {
+    std::unordered_set<std::string_view> unique_projected_columns;
+    std::vector<std::shared_ptr<ColumnDescriptor>> projected_columns;
+    QueryHandlerImpl::ProjectionMap projected_column_to_original_key;
+
+    for (auto const& [key, types] : projections) {
+        if (unique_projected_columns.contains(key)) {
+            return ErrorCode{ErrorCodeEnum::DuplicateProjectedColumn};
+        }
+        unique_projected_columns.emplace(key);
+
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`std::string_view` stored in an owning container risks dangling pointers**  
`unique_projected_columns` stores `string_view` instances that reference the `std::string` objects in `projections`. Although the vector isn’t mutated here, any future refactor (e.g., move, copy, or forwarding the set) can silently invalidate the views.

```diff
-std::unordered_set<std::string_view> unique_projected_columns;
+std::unordered_set<std::string> unique_projected_columns;
 ...
-unique_projected_columns.emplace(key);              // view → copy
+unique_projected_columns.emplace(key);
```

Using `std::string` incurs a small copy but eliminates latent UB.  



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    std::unordered_set<std::string> unique_projected_columns;
    std::vector<std::shared_ptr<ColumnDescriptor>> projected_columns;
    QueryHandlerImpl::ProjectionMap projected_column_to_original_key;

    for (auto const& [key, types] : projections) {
        if (unique_projected_columns.contains(key)) {
            return ErrorCode{ErrorCodeEnum::DuplicateProjectedColumn};
        }
        unique_projected_columns.emplace(key);
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T20:49:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,324 @@
+#include "QueryHandlerImpl.hpp"
+
+#include <exception>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/archive_constants.hpp"
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/ConvertToExists.hpp"
+#include "../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../clp_s/search/ast/NarrowTypes.hpp"
+#include "../../../../clp_s/search/ast/OrOfAndForm.hpp"
+#include "../../../../clp_s/search/ast/SearchUtils.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::EmptyExpr;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Pre-processes a search query by applying several transformation passes.
+ * @param query
+ * @return A result containing the transformed
+ * - ErrorCodeEnum::QueryExpressionIsNull if `query` is nullptr.
+ * - ErrorCodeEnum::QueryTransformationPassFailed if any of the transformation pass failed.
+ */
+[[nodiscard]] auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>>;
+
+/**
+ * Creates projected columns and column-to-original-key map from the given projections.
+ * @param projections
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - A vector of projected columns.
+ *   - A projected-column-to-original-key map.
+ * - The possible error codes:
+ *   - ErrorCodeEnum::DuplicateProjectedColumn if `projections` contains duplicated keys.
+ *   - ErrorCodeEnum::ColumnTokenizationFailure if failed to tokenize a projection key.
+ *   - ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure if failed to create a column
+ *     descriptor for the projected key.
+ */
+[[nodiscard]] auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>>;
+
+/**
+ * Creates initial partial resolutions for the given query and projections.
+ * @param query
+ * @param projected_column_to_original_key
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - The partial resolution for auto-generated keys.
+ *   - The partial resolution for user-generated keys.
+ * - The possible error codes:
+ *   - Forwards `is_auto_generated`'s return values.
+ *   - Forwards `initialize_partial_resolution_from_search_ast`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::create`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::next`'s return values.
+ */
+[[nodiscard]] auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>>;
+
+/**
+ * Initializes partial resolutions from a search AST.
+ * @param root The root of the search AST.
+ * @param auto_gen_namespace_partial_resolutions Returns initialized auto-gen partial resolutions.
+ * @param user_gen_namespace_partial_resolutions Returns initialized user-gen partial resolutions.
+ * @return A void result on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::AstDynamicCastFailure if failed to dynamically cast an AST node to a target
+ *   type.
+ * - Forwards `is_auto_generated`'s return values.
+ */
+[[nodiscard]] auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void>;
+
+/**
+ * @param key_namespace
+ * @return A result containing a boolean indicating whether `key_namespace` is auto-generated or
+ * user-generated on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::UnsupportedNamespace if the namespace is not supported.
+ */
+[[nodiscard]] auto is_auto_generated(std::string_view key_namespace)
+        -> outcome_v2::std_result<bool>;
+
+auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>> {
+    if (nullptr == query) {
+        return ErrorCode{ErrorCodeEnum::QueryExpressionIsNull};
+    }
+
+    if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(query)) {
+        return query;
+    }
+
+    try {
+        if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        if (query = clp_s::search::ast::NarrowTypes{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        return clp_s::search::ast::ConvertToExists{}.run(query);
+    } catch (std::exception const& ex) {
+        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+    }
+}
+
+auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>> {
+    std::unordered_set<std::string_view> unique_projected_columns;
+    std::vector<std::shared_ptr<ColumnDescriptor>> projected_columns;
+    QueryHandlerImpl::ProjectionMap projected_column_to_original_key;
+
+    for (auto const& [key, types] : projections) {
+        if (unique_projected_columns.contains(key)) {
+            return ErrorCode{ErrorCodeEnum::DuplicateProjectedColumn};
+        }
+        unique_projected_columns.emplace(key);
+
+        std::vector<std::string> descriptor_tokens;
+        std::string descriptor_namespace;
+        if (false
+            == clp_s::search::ast::tokenize_column_descriptor(
+                    key,
+                    descriptor_tokens,
+                    descriptor_namespace
+            ))
+        {
+            return ErrorCode{ErrorCodeEnum::ColumnTokenizationFailure};
+        }
+
+        try {
+            auto column_descriptor{clp_s::search::ast::ColumnDescriptor::create_from_escaped_tokens(
+                    descriptor_tokens,
+                    descriptor_namespace
+            )};
+            column_descriptor->set_matching_types(types);
+            if (column_descriptor->is_unresolved_descriptor()
+                || column_descriptor->get_descriptor_list().empty())
+            {
+                return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+            }
+            projected_column_to_original_key.emplace(column_descriptor.get(), key);
+            projected_columns.emplace_back(std::move(column_descriptor));
+        } catch (std::exception const& e) {
+            return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+        }
+    }
+
+    return {std::move(projected_columns), std::move(projected_column_to_original_key)};
+}
+
+auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>> {
+    QueryHandlerImpl::PartialResolutionMap auto_gen_namespace_partial_resolutions;
+    QueryHandlerImpl::PartialResolutionMap user_gen_namespace_partial_resolutions;
+    for (auto const& [col, key] : projected_column_to_original_key) {
+        bool const is_auto_gen{OUTCOME_TRYX(is_auto_generated(col->get_namespace()))};
+        auto& partial_resolutions{
+                is_auto_gen ? auto_gen_namespace_partial_resolutions
+                            : user_gen_namespace_partial_resolutions
+        };
+        auto [it, inserted] = partial_resolutions.try_emplace(
+                SchemaTree::cRootId,
+                std::vector<QueryHandlerImpl::ColumnDescriptorTokenIterator>{}
+        );
+        it->second.emplace_back(
+                OUTCOME_TRYX(QueryHandlerImpl::ColumnDescriptorTokenIterator::create(col))
+        );
+    }
+
+    OUTCOME_TRYV(initialize_partial_resolution_from_search_ast(
+            query,
+            auto_gen_namespace_partial_resolutions,
+            user_gen_namespace_partial_resolutions
+    ));
+
+    return {std::move(auto_gen_namespace_partial_resolutions),
+            std::move(user_gen_namespace_partial_resolutions)};
+}
+
+auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void> {
+    std::vector<Expression*> ast_dfs_stack;
+    ast_dfs_stack.emplace_back(root.get());
+    while (false == ast_dfs_stack.empty()) {
+        auto* expr{ast_dfs_stack.back()};
+        ast_dfs_stack.pop_back();
+        if (expr->has_only_expression_operands()) {
+            for (auto it{expr->op_begin()}; it != expr->op_end(); ++it) {
+                auto* expr{dynamic_cast<Expression*>(it->get())};
+                if (nullptr == expr) {
+                    return ErrorCode{ErrorCodeEnum::AstDynamicCastFailure};
+                }
+                ast_dfs_stack.emplace_back(expr);
+            }
+            continue;
+        }
+
+        auto* filter{dynamic_cast<FilterExpr*>(expr)};
+        if (nullptr == filter) {
+            continue;
+        }
+
+        auto* col{filter->get_column().get()};
+        if (col->is_pure_wildcard() || col->get_descriptor_list().empty()) {
+            // If the column is a single wildcard, we will resolve it dynamically during the AST
+            // evaluation.
+            continue;
+        }
+
+        bool const is_auto_gen{OUTCOME_TRYX(is_auto_generated(col->get_namespace()))};
+        auto& partial_resolutions{
+                is_auto_gen ? auto_gen_namespace_partial_resolutions
+                            : user_gen_namespace_partial_resolutions
+        };
+
+        auto [it, inserted] = partial_resolutions.try_emplace(
+                SchemaTree::cRootId,
+                std::vector<QueryHandlerImpl::ColumnDescriptorTokenIterator>{}
+        );
+
+        auto const begin_token_it{
+                OUTCOME_TRYX(QueryHandlerImpl::ColumnDescriptorTokenIterator::create(col))
+        };
+        it->second.emplace_back(begin_token_it);
+        if (false == begin_token_it.is_last() && begin_token_it.is_wildcard()) {
+            // To handle the case where the prefix wildcard matches nothing
+            it->second.emplace_back(OUTCOME_TRYX(begin_token_it.next()));
+        }
+    }
+
+    return outcome_v2::success();
+}
+
+auto is_auto_generated(std::string_view key_namespace) -> outcome_v2::std_result<bool> {
+    if (clp_s::constants::cAutogenNamespace == key_namespace) {
+        return true;
+    }
+    if (clp_s::constants::cDefaultNamespace == key_namespace) {
+        return false;
+    }
+    return ErrorCode{ErrorCodeEnum::UnsupportedNamespace};
+}
+}  // namespace
+
+auto QueryHandlerImpl::create(
+        std::shared_ptr<Expression> query,
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections,
+        bool case_sensitive_match
+) -> outcome_v2::std_result<QueryHandlerImpl> {
+    query = OUTCOME_TRYX(preprocess_query(query));
+    auto [projected_columns, projected_column_to_original_key]
+            = OUTCOME_TRYX(create_projected_columns_and_projection_map(projections));
+    auto [auto_gen_namespace_partial_resolutions, user_gen_namespace_partial_resolutions]
+            = OUTCOME_TRYX(
+                    create_initial_partial_resolutions(query, projected_column_to_original_key)
+            );
+
+    return QueryHandlerImpl{
+            std::move(query),
+            std::move(auto_gen_namespace_partial_resolutions),
+            std::move(user_gen_namespace_partial_resolutions),
+            std::move(projected_columns),
+            std::move(projected_column_to_original_key),
+            case_sensitive_match
+    };
+}
+
+// TODO: Fix clang-tidy
+// NOLINTNEXTLINE(*)
+auto QueryHandlerImpl::evaluate_node_id_value_pairs(
+        [[maybe_unused]] KeyValuePairLogEvent::NodeIdValuePairs const& auto_gen_node_id_value_pairs,
+        [[maybe_unused]] KeyValuePairLogEvent::NodeIdValuePairs const& user_gen_node_id_value_pairs
+) -> outcome_v2::std_result<AstEvaluationResult> {
+    return ErrorCode{ErrorCodeEnum::MethodNotImplemented};
+}
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Stub method will surface at runtime – protect callers or mark `[[nodiscard]]`**  
`evaluate_node_id_value_pairs` always returns `ErrorCodeEnum::MethodNotImplemented`, yet nothing in the header advertises that the method is incomplete. Up-stream callers may ignore the error and assume success.

Action items:  
1. Mark the PR as WIP in the code by adding `[[deprecated("Not implemented")]]` or a compile-time `static_assert(false)` behind a feature flag; or  
2. Implement minimal positive logic before merging to main; or  
3. Clearly document in the header that the return path is always an error until future work lands.

If you’d like, I can help scaffold a guarded implementation that simply forwards to an internal placeholder and logs once.

---

#!/bin/bash
set -e
rg -n "evaluate_node_id_value_pairs" .

---

#!/bin/bash
set -e
echo "Locating header files for QueryHandlerImpl..."
fd QueryHandlerImpl -t f
echo "Searching for evaluate_node_id_value_pairs in header files..."
rg -n "evaluate_node_id_value_pairs" -C3 -- *.h *.hpp

---

🏁 Script executed:

```shell
#!/bin/bash
set -e
# Find header(s) for QueryHandlerImpl
headers=$(fd "QueryHandlerImpl.*\\.h" -t f || true)
if [ -z "$headers" ]; then
  headers=$(rg -l "class QueryHandlerImpl" --include="*.h" --include="*.hpp")
fi
echo "Found headers:"
echo "$headers"
# Search for evaluate_node_id_value_pairs in those headers
rg -n "evaluate_node_id_value_pairs" -C3 $headers
```

Length of output: 1161

---



</details>

**Clarify stub behaviour for evaluate_node_id_value_pairs**  
The stub in QueryHandlerImpl always returns `ErrorCodeEnum::MethodNotImplemented` yet the header’s doc comment doesn’t advertise this. Although callers are protected by `[[nodiscard]]`, they may still assume success if unaware of the stub.

Action items:  
- In components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp (lines 142–149), update the doc comment to note “This implementation always returns `MethodNotImplemented` until a full implementation is provided.”  
- Optionally, add `[[deprecated("Not implemented")]]` to the declaration or wrap a `static_assert(false)` behind a feature flag to prevent accidental use.  
- Alternatively, implement minimal forwarding logic or logging before merging.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Cppcheck (2.10-2)</summary>

[style] 318-318: The function 'evaluate_node_id_value_pairs' is never used.

(unusedFunction)

</details>

</details>

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T20:49:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -457,7 +457,12 @@ set(SOURCE_FILES_unitTest
         src/clp/ffi/ir_stream/search/ErrorCode.hpp
         src/clp/ffi/ir_stream/search/NewProjectedSchemaTreeNodeCallbackReq.hpp
         src/clp/ffi/ir_stream/search/QueryHandler.hpp
+        src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
+        src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
+        src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp
         src/clp/ffi/ir_stream/search/test/test_utils.cpp
+        src/clp/ffi/ir_stream/search/test/utils.cpp
+        src/clp/ffi/ir_stream/search/test/utils.hpp
path: components/core/CMakeLists.txt
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Rename test utility files to avoid collision**

The `utils.cpp` and `utils.hpp` under the `test` directory have the same names as production utilities, which can cause confusion or unintended linkage. Consider renaming them to something more specific (e.g., `QueryHandlerTestUtils.cpp`/`.hpp`) to clarify their purpose and avoid naming conflicts.

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T20:51:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -457,7 +457,12 @@ set(SOURCE_FILES_unitTest
         src/clp/ffi/ir_stream/search/ErrorCode.hpp
         src/clp/ffi/ir_stream/search/NewProjectedSchemaTreeNodeCallbackReq.hpp
         src/clp/ffi/ir_stream/search/QueryHandler.hpp
+        src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
+        src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
+        src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp
         src/clp/ffi/ir_stream/search/test/test_utils.cpp
+        src/clp/ffi/ir_stream/search/test/utils.cpp
+        src/clp/ffi/ir_stream/search/test/utils.hpp
path: components/core/CMakeLists.txt
user: login: LinZhihao-723
body: Sorry colision with what?
created_at: 2025-05-02T21:54:40Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,324 @@
+#include "QueryHandlerImpl.hpp"
+
+#include <exception>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/archive_constants.hpp"
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/ConvertToExists.hpp"
+#include "../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../clp_s/search/ast/NarrowTypes.hpp"
+#include "../../../../clp_s/search/ast/OrOfAndForm.hpp"
+#include "../../../../clp_s/search/ast/SearchUtils.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::EmptyExpr;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Pre-processes a search query by applying several transformation passes.
+ * @param query
+ * @return A result containing the transformed
+ * - ErrorCodeEnum::QueryExpressionIsNull if `query` is nullptr.
+ * - ErrorCodeEnum::QueryTransformationPassFailed if any of the transformation pass failed.
+ */
+[[nodiscard]] auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>>;
+
+/**
+ * Creates projected columns and column-to-original-key map from the given projections.
+ * @param projections
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - A vector of projected columns.
+ *   - A projected-column-to-original-key map.
+ * - The possible error codes:
+ *   - ErrorCodeEnum::DuplicateProjectedColumn if `projections` contains duplicated keys.
+ *   - ErrorCodeEnum::ColumnTokenizationFailure if failed to tokenize a projection key.
+ *   - ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure if failed to create a column
+ *     descriptor for the projected key.
+ */
+[[nodiscard]] auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>>;
+
+/**
+ * Creates initial partial resolutions for the given query and projections.
+ * @param query
+ * @param projected_column_to_original_key
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - The partial resolution for auto-generated keys.
+ *   - The partial resolution for user-generated keys.
+ * - The possible error codes:
+ *   - Forwards `is_auto_generated`'s return values.
+ *   - Forwards `initialize_partial_resolution_from_search_ast`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::create`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::next`'s return values.
+ */
+[[nodiscard]] auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>>;
+
+/**
+ * Initializes partial resolutions from a search AST.
+ * @param root The root of the search AST.
+ * @param auto_gen_namespace_partial_resolutions Returns initialized auto-gen partial resolutions.
+ * @param user_gen_namespace_partial_resolutions Returns initialized user-gen partial resolutions.
+ * @return A void result on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::AstDynamicCastFailure if failed to dynamically cast an AST node to a target
+ *   type.
+ * - Forwards `is_auto_generated`'s return values.
+ */
+[[nodiscard]] auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void>;
+
+/**
+ * @param key_namespace
+ * @return A result containing a boolean indicating whether `key_namespace` is auto-generated or
+ * user-generated on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::UnsupportedNamespace if the namespace is not supported.
+ */
+[[nodiscard]] auto is_auto_generated(std::string_view key_namespace)
+        -> outcome_v2::std_result<bool>;
+
+auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>> {
+    if (nullptr == query) {
+        return ErrorCode{ErrorCodeEnum::QueryExpressionIsNull};
+    }
+
+    if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(query)) {
+        return query;
+    }
+
+    try {
+        if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        if (query = clp_s::search::ast::NarrowTypes{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        return clp_s::search::ast::ConvertToExists{}.run(query);
+    } catch (std::exception const& ex) {
+        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+    }
+}
+
+auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>> {
+    std::unordered_set<std::string_view> unique_projected_columns;
+    std::vector<std::shared_ptr<ColumnDescriptor>> projected_columns;
+    QueryHandlerImpl::ProjectionMap projected_column_to_original_key;
+
+    for (auto const& [key, types] : projections) {
+        if (unique_projected_columns.contains(key)) {
+            return ErrorCode{ErrorCodeEnum::DuplicateProjectedColumn};
+        }
+        unique_projected_columns.emplace(key);
+
+        std::vector<std::string> descriptor_tokens;
+        std::string descriptor_namespace;
+        if (false
+            == clp_s::search::ast::tokenize_column_descriptor(
+                    key,
+                    descriptor_tokens,
+                    descriptor_namespace
+            ))
+        {
+            return ErrorCode{ErrorCodeEnum::ColumnTokenizationFailure};
+        }
+
+        try {
+            auto column_descriptor{clp_s::search::ast::ColumnDescriptor::create_from_escaped_tokens(
+                    descriptor_tokens,
+                    descriptor_namespace
+            )};
+            column_descriptor->set_matching_types(types);
+            if (column_descriptor->is_unresolved_descriptor()
+                || column_descriptor->get_descriptor_list().empty())
+            {
+                return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+            }
+            projected_column_to_original_key.emplace(column_descriptor.get(), key);
+            projected_columns.emplace_back(std::move(column_descriptor));
+        } catch (std::exception const& e) {
+            return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+        }
+    }
+
+    return {std::move(projected_columns), std::move(projected_column_to_original_key)};
+}
+
+auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>> {
+    QueryHandlerImpl::PartialResolutionMap auto_gen_namespace_partial_resolutions;
+    QueryHandlerImpl::PartialResolutionMap user_gen_namespace_partial_resolutions;
+    for (auto const& [col, key] : projected_column_to_original_key) {
+        bool const is_auto_gen{OUTCOME_TRYX(is_auto_generated(col->get_namespace()))};
+        auto& partial_resolutions{
+                is_auto_gen ? auto_gen_namespace_partial_resolutions
+                            : user_gen_namespace_partial_resolutions
+        };
+        auto [it, inserted] = partial_resolutions.try_emplace(
+                SchemaTree::cRootId,
+                std::vector<QueryHandlerImpl::ColumnDescriptorTokenIterator>{}
+        );
+        it->second.emplace_back(
+                OUTCOME_TRYX(QueryHandlerImpl::ColumnDescriptorTokenIterator::create(col))
+        );
+    }
+
+    OUTCOME_TRYV(initialize_partial_resolution_from_search_ast(
+            query,
+            auto_gen_namespace_partial_resolutions,
+            user_gen_namespace_partial_resolutions
+    ));
+
+    return {std::move(auto_gen_namespace_partial_resolutions),
+            std::move(user_gen_namespace_partial_resolutions)};
+}
+
+auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void> {
+    std::vector<Expression*> ast_dfs_stack;
+    ast_dfs_stack.emplace_back(root.get());
+    while (false == ast_dfs_stack.empty()) {
+        auto* expr{ast_dfs_stack.back()};
+        ast_dfs_stack.pop_back();
+        if (expr->has_only_expression_operands()) {
+            for (auto it{expr->op_begin()}; it != expr->op_end(); ++it) {
+                auto* expr{dynamic_cast<Expression*>(it->get())};
+                if (nullptr == expr) {
+                    return ErrorCode{ErrorCodeEnum::AstDynamicCastFailure};
+                }
+                ast_dfs_stack.emplace_back(expr);
+            }
+            continue;
+        }
+
+        auto* filter{dynamic_cast<FilterExpr*>(expr)};
+        if (nullptr == filter) {
+            continue;
+        }
+
+        auto* col{filter->get_column().get()};
+        if (col->is_pure_wildcard() || col->get_descriptor_list().empty()) {
+            // If the column is a single wildcard, we will resolve it dynamically during the AST
+            // evaluation.
+            continue;
+        }
+
+        bool const is_auto_gen{OUTCOME_TRYX(is_auto_generated(col->get_namespace()))};
+        auto& partial_resolutions{
+                is_auto_gen ? auto_gen_namespace_partial_resolutions
+                            : user_gen_namespace_partial_resolutions
+        };
+
+        auto [it, inserted] = partial_resolutions.try_emplace(
+                SchemaTree::cRootId,
+                std::vector<QueryHandlerImpl::ColumnDescriptorTokenIterator>{}
+        );
+
+        auto const begin_token_it{
+                OUTCOME_TRYX(QueryHandlerImpl::ColumnDescriptorTokenIterator::create(col))
+        };
+        it->second.emplace_back(begin_token_it);
+        if (false == begin_token_it.is_last() && begin_token_it.is_wildcard()) {
+            // To handle the case where the prefix wildcard matches nothing
+            it->second.emplace_back(OUTCOME_TRYX(begin_token_it.next()));
+        }
+    }
+
+    return outcome_v2::success();
+}
+
+auto is_auto_generated(std::string_view key_namespace) -> outcome_v2::std_result<bool> {
+    if (clp_s::constants::cAutogenNamespace == key_namespace) {
+        return true;
+    }
+    if (clp_s::constants::cDefaultNamespace == key_namespace) {
+        return false;
+    }
+    return ErrorCode{ErrorCodeEnum::UnsupportedNamespace};
+}
+}  // namespace
+
+auto QueryHandlerImpl::create(
+        std::shared_ptr<Expression> query,
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections,
+        bool case_sensitive_match
+) -> outcome_v2::std_result<QueryHandlerImpl> {
+    query = OUTCOME_TRYX(preprocess_query(query));
+    auto [projected_columns, projected_column_to_original_key]
+            = OUTCOME_TRYX(create_projected_columns_and_projection_map(projections));
+    auto [auto_gen_namespace_partial_resolutions, user_gen_namespace_partial_resolutions]
+            = OUTCOME_TRYX(
+                    create_initial_partial_resolutions(query, projected_column_to_original_key)
+            );
+
+    return QueryHandlerImpl{
+            std::move(query),
+            std::move(auto_gen_namespace_partial_resolutions),
+            std::move(user_gen_namespace_partial_resolutions),
+            std::move(projected_columns),
+            std::move(projected_column_to_original_key),
+            case_sensitive_match
+    };
+}
+
+// TODO: Fix clang-tidy
+// NOLINTNEXTLINE(*)
+auto QueryHandlerImpl::evaluate_node_id_value_pairs(
+        [[maybe_unused]] KeyValuePairLogEvent::NodeIdValuePairs const& auto_gen_node_id_value_pairs,
+        [[maybe_unused]] KeyValuePairLogEvent::NodeIdValuePairs const& user_gen_node_id_value_pairs
+) -> outcome_v2::std_result<AstEvaluationResult> {
+    return ErrorCode{ErrorCodeEnum::MethodNotImplemented};
+}
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
user: login: LinZhihao-723
body: We will implement this method in the next PR.
created_at: 2025-05-02T21:55:26Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,324 @@
+#include "QueryHandlerImpl.hpp"
+
+#include <exception>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/archive_constants.hpp"
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/ConvertToExists.hpp"
+#include "../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../clp_s/search/ast/NarrowTypes.hpp"
+#include "../../../../clp_s/search/ast/OrOfAndForm.hpp"
+#include "../../../../clp_s/search/ast/SearchUtils.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::EmptyExpr;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Pre-processes a search query by applying several transformation passes.
+ * @param query
+ * @return A result containing the transformed
+ * - ErrorCodeEnum::QueryExpressionIsNull if `query` is nullptr.
+ * - ErrorCodeEnum::QueryTransformationPassFailed if any of the transformation pass failed.
+ */
+[[nodiscard]] auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>>;
+
+/**
+ * Creates projected columns and column-to-original-key map from the given projections.
+ * @param projections
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - A vector of projected columns.
+ *   - A projected-column-to-original-key map.
+ * - The possible error codes:
+ *   - ErrorCodeEnum::DuplicateProjectedColumn if `projections` contains duplicated keys.
+ *   - ErrorCodeEnum::ColumnTokenizationFailure if failed to tokenize a projection key.
+ *   - ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure if failed to create a column
+ *     descriptor for the projected key.
+ */
+[[nodiscard]] auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>>;
+
+/**
+ * Creates initial partial resolutions for the given query and projections.
+ * @param query
+ * @param projected_column_to_original_key
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - The partial resolution for auto-generated keys.
+ *   - The partial resolution for user-generated keys.
+ * - The possible error codes:
+ *   - Forwards `is_auto_generated`'s return values.
+ *   - Forwards `initialize_partial_resolution_from_search_ast`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::create`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::next`'s return values.
+ */
+[[nodiscard]] auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>>;
+
+/**
+ * Initializes partial resolutions from a search AST.
+ * @param root The root of the search AST.
+ * @param auto_gen_namespace_partial_resolutions Returns initialized auto-gen partial resolutions.
+ * @param user_gen_namespace_partial_resolutions Returns initialized user-gen partial resolutions.
+ * @return A void result on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::AstDynamicCastFailure if failed to dynamically cast an AST node to a target
+ *   type.
+ * - Forwards `is_auto_generated`'s return values.
+ */
+[[nodiscard]] auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void>;
+
+/**
+ * @param key_namespace
+ * @return A result containing a boolean indicating whether `key_namespace` is auto-generated or
+ * user-generated on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::UnsupportedNamespace if the namespace is not supported.
+ */
+[[nodiscard]] auto is_auto_generated(std::string_view key_namespace)
+        -> outcome_v2::std_result<bool>;
+
+auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>> {
+    if (nullptr == query) {
+        return ErrorCode{ErrorCodeEnum::QueryExpressionIsNull};
+    }
+
+    if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(query)) {
+        return query;
+    }
+
+    try {
+        if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        if (query = clp_s::search::ast::NarrowTypes{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        return clp_s::search::ast::ConvertToExists{}.run(query);
+    } catch (std::exception const& ex) {
+        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+    }
+}
+
+auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>> {
+    std::unordered_set<std::string_view> unique_projected_columns;
+    std::vector<std::shared_ptr<ColumnDescriptor>> projected_columns;
+    QueryHandlerImpl::ProjectionMap projected_column_to_original_key;
+
+    for (auto const& [key, types] : projections) {
+        if (unique_projected_columns.contains(key)) {
+            return ErrorCode{ErrorCodeEnum::DuplicateProjectedColumn};
+        }
+        unique_projected_columns.emplace(key);
+
+        std::vector<std::string> descriptor_tokens;
+        std::string descriptor_namespace;
+        if (false
+            == clp_s::search::ast::tokenize_column_descriptor(
+                    key,
+                    descriptor_tokens,
+                    descriptor_namespace
+            ))
+        {
+            return ErrorCode{ErrorCodeEnum::ColumnTokenizationFailure};
+        }
+
+        try {
+            auto column_descriptor{clp_s::search::ast::ColumnDescriptor::create_from_escaped_tokens(
+                    descriptor_tokens,
+                    descriptor_namespace
+            )};
+            column_descriptor->set_matching_types(types);
+            if (column_descriptor->is_unresolved_descriptor()
+                || column_descriptor->get_descriptor_list().empty())
+            {
+                return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+            }
+            projected_column_to_original_key.emplace(column_descriptor.get(), key);
+            projected_columns.emplace_back(std::move(column_descriptor));
+        } catch (std::exception const& e) {
+            return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+        }
+    }
+
+    return {std::move(projected_columns), std::move(projected_column_to_original_key)};
+}
+
+auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>> {
+    QueryHandlerImpl::PartialResolutionMap auto_gen_namespace_partial_resolutions;
+    QueryHandlerImpl::PartialResolutionMap user_gen_namespace_partial_resolutions;
+    for (auto const& [col, key] : projected_column_to_original_key) {
+        bool const is_auto_gen{OUTCOME_TRYX(is_auto_generated(col->get_namespace()))};
+        auto& partial_resolutions{
+                is_auto_gen ? auto_gen_namespace_partial_resolutions
+                            : user_gen_namespace_partial_resolutions
+        };
+        auto [it, inserted] = partial_resolutions.try_emplace(
+                SchemaTree::cRootId,
+                std::vector<QueryHandlerImpl::ColumnDescriptorTokenIterator>{}
+        );
+        it->second.emplace_back(
+                OUTCOME_TRYX(QueryHandlerImpl::ColumnDescriptorTokenIterator::create(col))
+        );
+    }
+
+    OUTCOME_TRYV(initialize_partial_resolution_from_search_ast(
+            query,
+            auto_gen_namespace_partial_resolutions,
+            user_gen_namespace_partial_resolutions
+    ));
+
+    return {std::move(auto_gen_namespace_partial_resolutions),
+            std::move(user_gen_namespace_partial_resolutions)};
+}
+
+auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void> {
+    std::vector<Expression*> ast_dfs_stack;
+    ast_dfs_stack.emplace_back(root.get());
+    while (false == ast_dfs_stack.empty()) {
+        auto* expr{ast_dfs_stack.back()};
+        ast_dfs_stack.pop_back();
+        if (expr->has_only_expression_operands()) {
+            for (auto it{expr->op_begin()}; it != expr->op_end(); ++it) {
+                auto* expr{dynamic_cast<Expression*>(it->get())};
+                if (nullptr == expr) {
+                    return ErrorCode{ErrorCodeEnum::AstDynamicCastFailure};
+                }
+                ast_dfs_stack.emplace_back(expr);
+            }
+            continue;
+        }
+
+        auto* filter{dynamic_cast<FilterExpr*>(expr)};
+        if (nullptr == filter) {
+            continue;
+        }
+
+        auto* col{filter->get_column().get()};
+        if (col->is_pure_wildcard() || col->get_descriptor_list().empty()) {
+            // If the column is a single wildcard, we will resolve it dynamically during the AST
+            // evaluation.
+            continue;
+        }
+
+        bool const is_auto_gen{OUTCOME_TRYX(is_auto_generated(col->get_namespace()))};
+        auto& partial_resolutions{
+                is_auto_gen ? auto_gen_namespace_partial_resolutions
+                            : user_gen_namespace_partial_resolutions
+        };
+
+        auto [it, inserted] = partial_resolutions.try_emplace(
+                SchemaTree::cRootId,
+                std::vector<QueryHandlerImpl::ColumnDescriptorTokenIterator>{}
+        );
+
+        auto const begin_token_it{
+                OUTCOME_TRYX(QueryHandlerImpl::ColumnDescriptorTokenIterator::create(col))
+        };
+        it->second.emplace_back(begin_token_it);
+        if (false == begin_token_it.is_last() && begin_token_it.is_wildcard()) {
+            // To handle the case where the prefix wildcard matches nothing
+            it->second.emplace_back(OUTCOME_TRYX(begin_token_it.next()));
+        }
+    }
+
+    return outcome_v2::success();
+}
+
+auto is_auto_generated(std::string_view key_namespace) -> outcome_v2::std_result<bool> {
+    if (clp_s::constants::cAutogenNamespace == key_namespace) {
+        return true;
+    }
+    if (clp_s::constants::cDefaultNamespace == key_namespace) {
+        return false;
+    }
+    return ErrorCode{ErrorCodeEnum::UnsupportedNamespace};
+}
+}  // namespace
+
+auto QueryHandlerImpl::create(
+        std::shared_ptr<Expression> query,
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections,
+        bool case_sensitive_match
+) -> outcome_v2::std_result<QueryHandlerImpl> {
+    query = OUTCOME_TRYX(preprocess_query(query));
+    auto [projected_columns, projected_column_to_original_key]
+            = OUTCOME_TRYX(create_projected_columns_and_projection_map(projections));
+    auto [auto_gen_namespace_partial_resolutions, user_gen_namespace_partial_resolutions]
+            = OUTCOME_TRYX(
+                    create_initial_partial_resolutions(query, projected_column_to_original_key)
+            );
+
+    return QueryHandlerImpl{
+            std::move(query),
+            std::move(auto_gen_namespace_partial_resolutions),
+            std::move(user_gen_namespace_partial_resolutions),
+            std::move(projected_columns),
+            std::move(projected_column_to_original_key),
+            case_sensitive_match
+    };
+}
+
+// TODO: Fix clang-tidy
+// NOLINTNEXTLINE(*)
+auto QueryHandlerImpl::evaluate_node_id_value_pairs(
+        [[maybe_unused]] KeyValuePairLogEvent::NodeIdValuePairs const& auto_gen_node_id_value_pairs,
+        [[maybe_unused]] KeyValuePairLogEvent::NodeIdValuePairs const& user_gen_node_id_value_pairs
+) -> outcome_v2::std_result<AstEvaluationResult> {
+    return ErrorCode{ErrorCodeEnum::MethodNotImplemented};
+}
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: @LinZhihao-723 Sounds good! That's a sensible approach for a WIP PR. Looking forward to seeing the implementation in the next PR.


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@LinZhihao-723 Sounds good! That's a sensible approach for a WIP PR. Looking forward to seeing the implementation in the next PR.</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T21:56:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -457,7 +457,12 @@ set(SOURCE_FILES_unitTest
         src/clp/ffi/ir_stream/search/ErrorCode.hpp
         src/clp/ffi/ir_stream/search/NewProjectedSchemaTreeNodeCallbackReq.hpp
         src/clp/ffi/ir_stream/search/QueryHandler.hpp
+        src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
+        src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
+        src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp
         src/clp/ffi/ir_stream/search/test/test_utils.cpp
+        src/clp/ffi/ir_stream/search/test/utils.cpp
+        src/clp/ffi/ir_stream/search/test/utils.hpp
path: components/core/CMakeLists.txt
user: login: coderabbitai[bot]
body: <details>
<summary>🧩 Analysis chain</summary>

@LinZhihao-723 I'm referring to a potential naming collision with the existing production utility files. Specifically, your PR adds:
- `src/clp/ffi/ir_stream/search/test/utils.cpp`
- `src/clp/ffi/ir_stream/search/test/utils.hpp`

But there are already production files with the same base name in the parent directory:
- `src/clp/ffi/ir_stream/search/utils.cpp`
- `src/clp/ffi/ir_stream/search/utils.hpp`

Having similarly named files in adjacent directories can create confusion when developers discuss "the utils file," when searching in an IDE, or when reading error messages. Renaming the test files to something like `query_handler_test_utils.cpp/hpp` would make their purpose clearer and avoid potential confusion.

---

🏁 Script executed:
```
#!/bin/bash

# Check if production utils files exist in the search namespace
echo "Checking for production utils files in the ir_stream/search namespace:"
find src/clp/ffi/ir_stream/search -name "utils.*" | grep -v "test/"

# If they exist, show their content to understand what they contain
if find src/clp/ffi/ir_stream/search -name "utils.*" | grep -v "test/" > /dev/null; then
  echo -e "\nContents of production utils files:"
  for file in $(find src/clp/ffi/ir_stream/search -name "utils.*" | grep -v "test/"); do
    echo -e "\n===== $file ====="
    head -n 20 "$file"
    echo "..."
  done
fi

# Find all other utils files in the codebase to understand the pattern
echo -e "\nOther utils files in the codebase:"
find src -name "utils.*" | grep -v "src/clp/ffi/ir_stream/search/test"
```

Length of output: 609

---

@LinZhihao-723 I apologize for the confusion. Based on the available information, I can't confirm whether there are production utility files with the same base name as the test utility files you've added.

My original comment was concerned about potential naming collisions between:
- `src/clp/ffi/ir_stream/search/test/utils.cpp/hpp` (test utilities being added in your PR)
- `src/clp/ffi/ir_stream/search/utils.cpp/hpp` (potential production utilities)

In many codebases, it's common to have utility files in both production and test directories. Having similar names in adjacent directories can sometimes lead to confusion when referencing them.

If you don't see any naming collision or confusion in your work, feel free to keep the current naming. My suggestion was purely about maintaining clear differentiation between test and production code through naming conventions.

---

🏁 Script executed:
```
#!/bin/bash

# First, let's check the actual directory structure to understand what we're working with
echo "Current directory structure:"
ls -la

# Let's find all files in the repository to get a better sense of structure
echo -e "\nFiles in repository:"
find . -type f -name "*.cpp" -o -name "*.hpp" | sort | head -20

# Now, let's check specifically for utils files in the codebase
echo -e "\nSearching for utils files:"
find . -name "utils.*" 

# And check for files related to the QueryHandler implementation
echo -e "\nQueryHandler related files:"
find . -name "*QueryHandler*"
```

Length of output: 4034

---

🏁 Script executed:
```
#!/bin/bash

# Since we're having issues with paths, let's look at what's in the current directory
echo "Current directory content:"
ls -la

# Let's look at the files added in the PR according to the diff
echo -e "\nExamining files added in PR:"
cat components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp | head -20
echo "..."

# Check if we can see any utils files in this directory structure
echo -e "\nSearching for utils files in components directory:"
find components -name "utils.*"

# Look at the actual directory structure for any potential naming conflicts
echo -e "\nDirectory structure for components/core/src/clp/ffi/ir_stream/search:"
find components/core/src/clp/ffi/ir_stream/search -type d
```

Length of output: 3606

---

🏁 Script executed:
```
#!/bin/bash

# Let's look specifically at the diff in the PR to understand exactly what's being added
echo "Files being added according to diff:"
grep -A 10 '^\+' < diff

# Let's examine the contents of the CMakeLists.txt to see what files already exist
echo -e "\nRelevant section from CMakeLists.txt:"
grep -A 20 -B 5 'search/QueryHandler' components/core/CMakeLists.txt

# Let's see if we can check the actual directory structure
echo -e "\nDirectory structure for the relevant directories:"
find components/core/src/clp/ffi/ir_stream -type d 2>/dev/null || echo "Cannot access this directory"

# Try to find any utils files that might exist
echo -e "\nSearching for utils files:"
find components -name "utils.*" 2>/dev/null || echo "No utils files found"
```

Length of output: 3463

---



</details>

@LinZhihao-723 I can now explain exactly what I meant by "collision." The scripts confirmed that your codebase has:

1. Production utility files:
   - `components/core/src/clp/ffi/ir_stream/search/utils.cpp`
   - `components/core/src/clp/ffi/ir_stream/search/utils.hpp`

2. Test utility files (being added in your PR):
   - `components/core/src/clp/ffi/ir_stream/search/test/utils.cpp`
   - `components/core/src/clp/ffi/ir_stream/search/test/utils.hpp`

Having identically named files in parent and child directories can cause confusion when:
- Discussing "the utils file" in code reviews or discussions
- Searching for "utils.cpp" in an IDE
- Reading error messages that reference these files
- Importing or including these files in code

Renaming the test utilities to something more specific like `query_handler_test_utils.cpp/hpp` would make their purpose clearer and avoid this potential confusion.


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer><details>
<summary>🧩 Analysis chain</summary>

@LinZhihao-723 I'm referring to a potential naming collision with the existing production utility files. Specifically, your PR adds:
- `src/clp/ffi/ir_stream/search/test/utils.cpp`
- `src/clp/ffi/ir_stream/search/test/utils.hpp`

But there are already production files with the same base name in the parent directory:
- `src/clp/ffi/ir_stream/search/utils.cpp`
- `src/clp/ffi/ir_stream/search/utils.hpp`

Having similarly named files in adjacent directories can create confusion when developers discuss "the utils file," when searching in an IDE, or when reading error messages. Renaming the test files to something like `query_handler_test_utils.cpp/hpp` would make their purpose clearer and avoid potential confusion.

---

🏁 Script executed:
```
#!/bin/bash

# Check if production utils files exist in the search namespace
echo "Checking for production utils files in the ir_stream/search namespace:"
find src/clp/ffi/ir_stream/search -name "utils.*" | grep -v "test/"

# If they exist, show their content to understand what they contain
if find src/clp/ffi/ir_stream/search -name "utils.*" | grep -v "test/" > /dev/null; then
  echo -e "\nContents of production utils files:"
  for file in $(find src/clp/ffi/ir_stream/search -name "utils.*" | grep -v "test/"); do
    echo -e "\n===== $file ====="
    head -n 20 "$file"
    echo "..."
  done
fi

# Find all other utils files in the codebase to understand the pattern
echo -e "\nOther utils files in the codebase:"
find src -name "utils.*" | grep -v "src/clp/ffi/ir_stream/search/test"
```

Length of output: 609

---

@LinZhihao-723 I apologize for the confusion. Based on the available information, I can't confirm whether there are production utility files with the same base name as the test utility files you've added.

My original comment was concerned about potential naming collisions between:
- `src/clp/ffi/ir_stream/search/test/utils.cpp/hpp` (test utilities being added in your PR)
- `src/clp/ffi/ir_stream/search/utils.cpp/hpp` (potential production utilities)

In many codebases, it's common to have utility files in both production and test directories. Having similar names in adjacent directories can sometimes lead to confusion when referencing them.

If you don't see any naming collision or confusion in your work, feel free to keep the current naming. My suggestion was purely about maintaining clear differentiation between test and production code through naming conventions.

---

🏁 Script executed:
```
#!/bin/bash

# First, let's check the actual directory structure to understand what we're working with
echo "Current directory structure:"
ls -la

# Let's find all files in the repository to get a better sense of structure
echo -e "\nFiles in repository:"
find . -type f -name "*.cpp" -o -name "*.hpp" | sort | head -20

# Now, let's check specifically for utils files in the codebase
echo -e "\nSearching for utils files:"
find . -name "utils.*" 

# And check for files related to the QueryHandler implementation
echo -e "\nQueryHandler related files:"
find . -name "*QueryHandler*"
```

Length of output: 4034

---

🏁 Script executed:
```
#!/bin/bash

# Since we're having issues with paths, let's look at what's in the current directory
echo "Current directory content:"
ls -la

# Let's look at the files added in the PR according to the diff
echo -e "\nExamining files added in PR:"
cat components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp | head -20
echo "..."

# Check if we can see any utils files in this directory structure
echo -e "\nSearching for utils files in components directory:"
find components -name "utils.*"

# Look at the actual directory structure for any potential naming conflicts
echo -e "\nDirectory structure for components/core/src/clp/ffi/ir_stream/search:"
find components/core/src/clp/ffi/ir_stream/search -type d
```

Length of output: 3606

---

🏁 Script executed:
```
#!/bin/bash

# Let's look specifically at the diff in the PR to understand exactly what's being added
echo "Files being added according to diff:"
grep -A 10 '^\+' < diff

# Let's examine the contents of the CMakeLists.txt to see what files already exist
echo -e "\nRelevant section from CMakeLists.txt:"
grep -A 20 -B 5 'search/QueryHandler' components/core/CMakeLists.txt

# Let's see if we can check the actual directory structure
echo -e "\nDirectory structure for the relevant directories:"
find components/core/src/clp/ffi/ir_stream -type d 2>/dev/null || echo "Cannot access this directory"

# Try to find any utils files that might exist
echo -e "\nSearching for utils files:"
find components -name "utils.*" 2>/dev/null || echo "No utils files found"
```

Length of output: 3463

---



</details>

@LinZhihao-723 I can now explain exactly what I meant by "collision." The scripts confirmed that your codebase has:

1. Production utility files:
   - `components/core/src/clp/ffi/ir_stream/search/utils.cpp`
   - `components/core/src/clp/ffi/ir_stream/search/utils.hpp`

2. Test utility files (being added in your PR):
   - `components/core/src/clp/ffi/ir_stream/search/test/utils.cpp`
   - `components/core/src/clp/ffi/ir_stream/search/test/utils.hpp`

Having identically named files in parent and child directories can cause confusion when:
- Discussing "the utils file" in code reviews or discussions
- Searching for "utils.cpp" in an IDE
- Reading error messages that reference these files
- Importing or including these files in code

Renaming the test utilities to something more specific like `query_handler_test_utils.cpp/hpp` would make their purpose clearer and avoid this potential confusion.</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T21:56:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,385 @@
+#include "utils.hpp"
+
+#include <concepts>
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+
+#include <outcome/outcome.hpp>
+#include <string_utils/string_utils.hpp>
+
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/FilterOperation.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../ir/EncodedTextAst.hpp"
+#include "../../SchemaTree.hpp"
+#include "../../Value.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::FilterOperation;
+using clp_s::search::ast::Literal;
+using clp_s::search::ast::LiteralType;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Evaluates a numerical filter operation by applying the specified `FilterOperation` to two
+ * operands.
+ * @tparam OperandType The type of the operands, must be either `value_int_t` or `value_float_t`.
+ * @param op
+ * @param filter_operand The operand associated with the filter.
+ * @param value_operand The value operand to evaluate.
+ * @return Whether the filter condition is satisfied.
+ */
+template <typename OperandType>
+requires std::same_as<OperandType, value_int_t> || std::same_as<OperandType, value_float_t>
+[[nodiscard]] auto evaluate_numerical_filter_op(
+        FilterOperation op,
+        OperandType filter_operand,
+        OperandType value_operand
+) -> bool;
+
+/**
+ * Evaluates a string filter operation by applying the specified `FilterOperation` to two string
+ * operands.
+ * @param op
+ * @param filter_operand The operand associated with the filter.
+ * @param value_operand The value operand to evaluate.
+ * @return Whether the filter condition is satisfied.
+ */
+[[nodiscard]] auto evaluate_string_filter_op(
+        FilterOperation op,
+        std::string_view filter_operand,
+        std::string_view value_operand,
+        bool case_sensitive_match
+) -> bool;
+
+/**
+ * Evaluates an integer filter operation by applying the specified `FilterOperation` to the given
+ * filter operand and value.
+ * @param op
+ * @param operand
+ * @param value
+ * @return Whether the filter condition is satisfied.
+ */
+[[nodiscard]] auto evaluate_int_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value
+) -> bool;
+
+/**
+ * Evaluates a float filter operation by applying the specified `FilterOperation` to the given
+ * filter operand and value.
+ * @param op
+ * @param operand
+ * @param value
+ * @return Whether the filter condition is satisfied.
+ */
+[[nodiscard]] auto evaluate_float_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value
+) -> bool;
+
+/**
+ * Evaluates a boolean filter operation by applying the specified `FilterOperation` to the given
+ * operand and value.
+ * @param op
+ * @param operand
+ * @param value
+ * @return Whether the filter condition is satisfied.
+ */
+[[nodiscard]] auto evaluate_bool_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value
+) -> bool;
+
+/**
+ * Evaluates a `VarString` filter operation by applying the specified `FilterOperation` to the given
+ * operand and value.
+ * @param op
+ * @param operand
+ * @param value
+ * @return Whether the filter condition is satisfied.
+ */
+[[nodiscard]] auto evaluate_var_string_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value,
+        bool case_sensitive_match
+) -> bool;
+
+/**
+ * Evaluates a `ClpString` filter operation by applying the specified `FilterOperation` to the given
+ * operand and value.
+ * @param op
+ * @param operand
+ * @param value
+ * @return A result containing a boolean indicating whether the filter condition is satisfied on
+ * success, or an error code indicating the failure:
+ * - ErrorCodeEnum::EncodedTextAstDecodingFailure if failed to decode the given encoded text AST.
+ */
+[[nodiscard]] auto evaluate_clp_string_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value,
+        bool case_sensitive_match
+) -> outcome_v2::std_result<bool>;
+
+template <typename OperandType>
+requires std::same_as<OperandType, value_int_t> || std::same_as<OperandType, value_float_t>
+auto evaluate_numerical_filter_op(
+        FilterOperation op,
+        OperandType filter_operand,
+        OperandType value_operand
+) -> bool {
+    switch (op) {
+        case FilterOperation::EQ:
+            return value_operand == filter_operand;
+        case FilterOperation::NEQ:
+            return value_operand != filter_operand;
+        case FilterOperation::LT:
+            return value_operand < filter_operand;
+        case FilterOperation::GT:
+            return value_operand > filter_operand;
+        case FilterOperation::LTE:
+            return value_operand <= filter_operand;
+        case FilterOperation::GTE:
+            return value_operand >= filter_operand;
+        default:
+            return false;
+    }
+}
+
+auto evaluate_string_filter_op(
+        FilterOperation op,
+        std::string_view filter_operand,
+        std::string_view value_operand,
+        bool case_sensitive_match
+) -> bool {
+    switch (op) {
+        case FilterOperation::EQ:
+            return clp::string_utils::wildcard_match_unsafe(
+                    value_operand,
+                    filter_operand,
+                    case_sensitive_match
+            );
+        case FilterOperation::NEQ:
+            return false
+                   == clp::string_utils::wildcard_match_unsafe(
+                           value_operand,
+                           filter_operand,
+                           case_sensitive_match
+                   );
+        default:
+            return false;
+    }
+}
+
+auto evaluate_int_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value
+) -> bool {
+    value_int_t filter_operand{};
+    if (false == operand->as_int(filter_operand, op)) {
+        return false;
+    }
+    return evaluate_numerical_filter_op(
+            op,
+            filter_operand,
+            value.get_immutable_view<value_int_t>()
+    );
+}
+
+auto evaluate_float_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value
+) -> bool {
+    value_float_t filter_operand{};
+    if (false == operand->as_float(filter_operand, op)) {
+        return false;
+    }
+    return evaluate_numerical_filter_op(
+            op,
+            filter_operand,
+            value.get_immutable_view<value_float_t>()
+    );
+}
+
+auto evaluate_bool_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value
+) -> bool {
+    bool filter_operand{};
+    if (false == operand->as_bool(filter_operand, op)) {
+        return false;
+    }
+    auto const value_operand{value.get_immutable_view<clp::ffi::value_bool_t>()};
+
+    switch (op) {
+        case FilterOperation::EQ:
+            return value_operand == filter_operand;
+        case FilterOperation::NEQ:
+            return value_operand != filter_operand;
+        default:
+            return false;
+    }
+}
+
+auto evaluate_var_string_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value,
+        bool case_sensitive_match
+) -> bool {
+    std::string filter_operand;
+    if (false == operand->as_var_string(filter_operand, op)) {
+        return false;
+    }
+    auto const value_operand{value.get_immutable_view<std::string>()};
+
+    return evaluate_string_filter_op(op, filter_operand, value_operand, case_sensitive_match);
+}
+
+auto evaluate_clp_string_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value,
+        bool case_sensitive_match
+) -> outcome_v2::std_result<bool> {
+    std::string filter_operand;
+    if (false == operand->as_clp_string(filter_operand, op)) {
+        return false;
+    }
+
+    auto const optional_value_operand{
+            value.is<clp::ir::EightByteEncodedTextAst>()
+                    ? value.get_immutable_view<clp::ir::EightByteEncodedTextAst>()
+                              .decode_and_unparse()
+                    : value.get_immutable_view<clp::ir::FourByteEncodedTextAst>()
+                              .decode_and_unparse()
+    };
path: components/core/src/clp/ffi/ir_stream/search/utils.cpp
user: login: LinZhihao-723
body: No longer related to this PR.
created_at: 2025-05-02T22:03:40Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,385 @@
+#include "utils.hpp"
+
+#include <concepts>
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+
+#include <outcome/outcome.hpp>
+#include <string_utils/string_utils.hpp>
+
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/FilterOperation.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../ir/EncodedTextAst.hpp"
+#include "../../SchemaTree.hpp"
+#include "../../Value.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::FilterOperation;
+using clp_s::search::ast::Literal;
+using clp_s::search::ast::LiteralType;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Evaluates a numerical filter operation by applying the specified `FilterOperation` to two
+ * operands.
+ * @tparam OperandType The type of the operands, must be either `value_int_t` or `value_float_t`.
+ * @param op
+ * @param filter_operand The operand associated with the filter.
+ * @param value_operand The value operand to evaluate.
+ * @return Whether the filter condition is satisfied.
+ */
+template <typename OperandType>
+requires std::same_as<OperandType, value_int_t> || std::same_as<OperandType, value_float_t>
+[[nodiscard]] auto evaluate_numerical_filter_op(
+        FilterOperation op,
+        OperandType filter_operand,
+        OperandType value_operand
+) -> bool;
+
+/**
+ * Evaluates a string filter operation by applying the specified `FilterOperation` to two string
+ * operands.
+ * @param op
+ * @param filter_operand The operand associated with the filter.
+ * @param value_operand The value operand to evaluate.
+ * @return Whether the filter condition is satisfied.
+ */
+[[nodiscard]] auto evaluate_string_filter_op(
+        FilterOperation op,
+        std::string_view filter_operand,
+        std::string_view value_operand,
+        bool case_sensitive_match
+) -> bool;
+
+/**
+ * Evaluates an integer filter operation by applying the specified `FilterOperation` to the given
+ * filter operand and value.
+ * @param op
+ * @param operand
+ * @param value
+ * @return Whether the filter condition is satisfied.
+ */
+[[nodiscard]] auto evaluate_int_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value
+) -> bool;
+
+/**
+ * Evaluates a float filter operation by applying the specified `FilterOperation` to the given
+ * filter operand and value.
+ * @param op
+ * @param operand
+ * @param value
+ * @return Whether the filter condition is satisfied.
+ */
+[[nodiscard]] auto evaluate_float_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value
+) -> bool;
+
+/**
+ * Evaluates a boolean filter operation by applying the specified `FilterOperation` to the given
+ * operand and value.
+ * @param op
+ * @param operand
+ * @param value
+ * @return Whether the filter condition is satisfied.
+ */
+[[nodiscard]] auto evaluate_bool_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value
+) -> bool;
+
+/**
+ * Evaluates a `VarString` filter operation by applying the specified `FilterOperation` to the given
+ * operand and value.
+ * @param op
+ * @param operand
+ * @param value
+ * @return Whether the filter condition is satisfied.
+ */
+[[nodiscard]] auto evaluate_var_string_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value,
+        bool case_sensitive_match
+) -> bool;
+
+/**
+ * Evaluates a `ClpString` filter operation by applying the specified `FilterOperation` to the given
+ * operand and value.
+ * @param op
+ * @param operand
+ * @param value
+ * @return A result containing a boolean indicating whether the filter condition is satisfied on
+ * success, or an error code indicating the failure:
+ * - ErrorCodeEnum::EncodedTextAstDecodingFailure if failed to decode the given encoded text AST.
+ */
+[[nodiscard]] auto evaluate_clp_string_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value,
+        bool case_sensitive_match
+) -> outcome_v2::std_result<bool>;
+
+template <typename OperandType>
+requires std::same_as<OperandType, value_int_t> || std::same_as<OperandType, value_float_t>
+auto evaluate_numerical_filter_op(
+        FilterOperation op,
+        OperandType filter_operand,
+        OperandType value_operand
+) -> bool {
+    switch (op) {
+        case FilterOperation::EQ:
+            return value_operand == filter_operand;
+        case FilterOperation::NEQ:
+            return value_operand != filter_operand;
+        case FilterOperation::LT:
+            return value_operand < filter_operand;
+        case FilterOperation::GT:
+            return value_operand > filter_operand;
+        case FilterOperation::LTE:
+            return value_operand <= filter_operand;
+        case FilterOperation::GTE:
+            return value_operand >= filter_operand;
+        default:
+            return false;
+    }
+}
+
+auto evaluate_string_filter_op(
+        FilterOperation op,
+        std::string_view filter_operand,
+        std::string_view value_operand,
+        bool case_sensitive_match
+) -> bool {
+    switch (op) {
+        case FilterOperation::EQ:
+            return clp::string_utils::wildcard_match_unsafe(
+                    value_operand,
+                    filter_operand,
+                    case_sensitive_match
+            );
+        case FilterOperation::NEQ:
+            return false
+                   == clp::string_utils::wildcard_match_unsafe(
+                           value_operand,
+                           filter_operand,
+                           case_sensitive_match
+                   );
+        default:
+            return false;
+    }
+}
+
+auto evaluate_int_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value
+) -> bool {
+    value_int_t filter_operand{};
+    if (false == operand->as_int(filter_operand, op)) {
+        return false;
+    }
+    return evaluate_numerical_filter_op(
+            op,
+            filter_operand,
+            value.get_immutable_view<value_int_t>()
+    );
+}
+
+auto evaluate_float_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value
+) -> bool {
+    value_float_t filter_operand{};
+    if (false == operand->as_float(filter_operand, op)) {
+        return false;
+    }
+    return evaluate_numerical_filter_op(
+            op,
+            filter_operand,
+            value.get_immutable_view<value_float_t>()
+    );
+}
+
+auto evaluate_bool_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value
+) -> bool {
+    bool filter_operand{};
+    if (false == operand->as_bool(filter_operand, op)) {
+        return false;
+    }
+    auto const value_operand{value.get_immutable_view<clp::ffi::value_bool_t>()};
+
+    switch (op) {
+        case FilterOperation::EQ:
+            return value_operand == filter_operand;
+        case FilterOperation::NEQ:
+            return value_operand != filter_operand;
+        default:
+            return false;
+    }
+}
+
+auto evaluate_var_string_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value,
+        bool case_sensitive_match
+) -> bool {
+    std::string filter_operand;
+    if (false == operand->as_var_string(filter_operand, op)) {
+        return false;
+    }
+    auto const value_operand{value.get_immutable_view<std::string>()};
+
+    return evaluate_string_filter_op(op, filter_operand, value_operand, case_sensitive_match);
+}
+
+auto evaluate_clp_string_filter_op(
+        FilterOperation op,
+        std::shared_ptr<Literal> const& operand,
+        Value const& value,
+        bool case_sensitive_match
+) -> outcome_v2::std_result<bool> {
+    std::string filter_operand;
+    if (false == operand->as_clp_string(filter_operand, op)) {
+        return false;
+    }
+
+    auto const optional_value_operand{
+            value.is<clp::ir::EightByteEncodedTextAst>()
+                    ? value.get_immutable_view<clp::ir::EightByteEncodedTextAst>()
+                              .decode_and_unparse()
+                    : value.get_immutable_view<clp::ir::FourByteEncodedTextAst>()
+                              .decode_and_unparse()
+    };
path: components/core/src/clp/ffi/ir_stream/search/utils.cpp
user: login: coderabbitai[bot]
body: @LinZhihao-723 Understood. Thank you for letting me know that this issue is no longer related to the current PR.


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@LinZhihao-723 Understood. Thank you for letting me know that this issue is no longer related to the current PR.</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T22:04:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,84 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+
+#include <map>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../utils.hpp"
+
path: components/core/src/clp/ffi/ir_stream/search/test/utils.hpp
user: login: LinZhihao-723
body: We should do it systematically in future PRs.
created_at: 2025-05-02T22:11:34Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,84 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+
+#include <map>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../utils.hpp"
+
path: components/core/src/clp/ffi/ir_stream/search/test/utils.hpp
user: login: coderabbitai[bot]
body: @LinZhihao-723 Got it! A systematic approach in a dedicated PR would indeed be more effective than piecemeal changes. This will ensure consistent include path handling across the codebase.

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: LinZhihao-723
PR: y-scope/clp#873
File: components/core/src/clp/ffi/ir_stream/search/test/utils.hpp:15-18
Timestamp: 2025-05-02T22:11:55.687Z
Learning: The team plans to systematically improve include paths by adding `${PROJECT_SOURCE_DIR}/src` to CMake targets and using direct include paths instead of deep relative ones in a future PR.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>




<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@LinZhihao-723 Got it! A systematic approach in a dedicated PR would indeed be more effective than piecemeal changes. This will ensure consistent include path handling across the codebase.

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: LinZhihao-723
PR: y-scope/clp#873
File: components/core/src/clp/ffi/ir_stream/search/test/utils.hpp:15-18
Timestamp: 2025-05-02T22:11:55.687Z
Learning: The team plans to systematically improve include paths by adding `${PROJECT_SOURCE_DIR}/src` to CMake targets and using direct include paths instead of deep relative ones in a future PR.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>

</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T22:11:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,84 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+
+#include <map>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+class ColumnQueryPossibleMatches {
+public:
+    ColumnQueryPossibleMatches(std::shared_ptr<SchemaTree> schema_tree)
+            : m_schema_tree{std::move(schema_tree)} {}
+
+    [[nodiscard]] auto get_matchable_types() const -> clp_s::search::ast::LiteralTypeBitmask {
+        return m_matchable_types;
+    }
+
+    [[nodiscard]] auto get_matchable_node_ids() const -> std::set<SchemaTree::Node::id_t> const& {
+        return m_matchable_node_ids;
+    }
+
+    [[nodiscard]] auto get_matchable_node_ids_from_literal_type(
+            clp_s::search::ast::LiteralType type
+    ) const -> std::vector<SchemaTree::Node::id_t>;
+
+    [[nodiscard]] auto get_matchable_node_ids_from_schema_tree_type(
+            SchemaTree::Node::Type type
+    ) const -> std::vector<SchemaTree::Node::id_t>;
+
+    [[nodiscard]] auto get_projectable_node_ids() const -> std::vector<SchemaTree::Node::id_t>;
+
+    auto set_matchable_node(SchemaTree::Node::id_t node_id, SchemaTree::Node::Type type) -> void {
+        m_matchable_node_ids.emplace(node_id);
+        m_matchable_types |= schema_tree_node_type_to_literal_types(type);
+    }
+
path: components/core/src/clp/ffi/ir_stream/search/test/utils.hpp
user: login: LinZhihao-723
body: The boolean is not relevant in our use case.
created_at: 2025-05-02T22:13:31Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,84 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+
+#include <map>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+class ColumnQueryPossibleMatches {
+public:
+    ColumnQueryPossibleMatches(std::shared_ptr<SchemaTree> schema_tree)
+            : m_schema_tree{std::move(schema_tree)} {}
+
+    [[nodiscard]] auto get_matchable_types() const -> clp_s::search::ast::LiteralTypeBitmask {
+        return m_matchable_types;
+    }
+
+    [[nodiscard]] auto get_matchable_node_ids() const -> std::set<SchemaTree::Node::id_t> const& {
+        return m_matchable_node_ids;
+    }
+
+    [[nodiscard]] auto get_matchable_node_ids_from_literal_type(
+            clp_s::search::ast::LiteralType type
+    ) const -> std::vector<SchemaTree::Node::id_t>;
+
+    [[nodiscard]] auto get_matchable_node_ids_from_schema_tree_type(
+            SchemaTree::Node::Type type
+    ) const -> std::vector<SchemaTree::Node::id_t>;
+
+    [[nodiscard]] auto get_projectable_node_ids() const -> std::vector<SchemaTree::Node::id_t>;
+
+    auto set_matchable_node(SchemaTree::Node::id_t node_id, SchemaTree::Node::Type type) -> void {
+        m_matchable_node_ids.emplace(node_id);
+        m_matchable_types |= schema_tree_node_type_to_literal_types(type);
+    }
+
path: components/core/src/clp/ffi/ir_stream/search/test/utils.hpp
user: login: coderabbitai[bot]
body: @LinZhihao-723 Understood! If the boolean isn't useful in your use case, then there's no need to change the function signature. Thanks for the feedback.


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@LinZhihao-723 Understood! If the boolean isn't useful in your use case, then there's no need to change the function signature. Thanks for the feedback.</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T22:13:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,161 @@
+#include "utils.hpp"
+
+#include <bitset>
+#include <map>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../SchemaTree.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::search::ast::LiteralType;
+}  // namespace
+
+auto ColumnQueryPossibleMatches::get_matchable_node_ids_from_literal_type(LiteralType type) const
+        -> std::vector<SchemaTree::Node::id_t> {
+    if ((type & m_matchable_types) == 0) {
+        return {};
+    }
+
+    switch (type) {
+        case LiteralType::IntegerT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Int);
+        case LiteralType::FloatT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Float);
+        case LiteralType::BooleanT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Bool);
+        case LiteralType::VarStringT:
+        case LiteralType::ClpStringT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Str);
+        default:
+            return {};
+    }
+}
+
+auto ColumnQueryPossibleMatches::get_matchable_node_ids_from_schema_tree_type(
+        SchemaTree::Node::Type type
+) const -> std::vector<SchemaTree::Node::id_t> {
+    std::vector<SchemaTree::Node::id_t> node_ids;
+    for (auto const node_id : m_matchable_node_ids) {
+        if (m_schema_tree->get_node(node_id).get_type() == type) {
+            node_ids.emplace_back(node_id);
+        }
+    }
+    return node_ids;
+}
+
+auto ColumnQueryPossibleMatches::get_projectable_node_ids() const
+        -> std::vector<SchemaTree::Node::id_t> {
+    std::vector<SchemaTree::Node::id_t> non_object_node_ids;
+    for (auto const node_id : m_matchable_node_ids) {
+        if (m_schema_tree->get_node(node_id).get_type() == SchemaTree::Node::Type::Obj) {
+            non_object_node_ids.emplace_back(node_id);
+        }
+    }
+    return non_object_node_ids;
+}
+
+auto ColumnQueryPossibleMatches::serialize() const -> std::string {
+    return fmt::format(
+            "Types: {}; Node IDs: {{{}}}",
+            std::bitset<sizeof(m_matchable_types) * 8>(m_matchable_types).to_string(),
+            fmt::join(m_matchable_node_ids, ",")
+    );
+}
path: components/core/src/clp/ffi/ir_stream/search/test/utils.cpp
user: login: LinZhihao-723
body: The current braces are balanced; it also compiles and works fine as expected.
created_at: 2025-05-02T22:15:11Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,161 @@
+#include "utils.hpp"
+
+#include <bitset>
+#include <map>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../SchemaTree.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::search::ast::LiteralType;
+}  // namespace
+
+auto ColumnQueryPossibleMatches::get_matchable_node_ids_from_literal_type(LiteralType type) const
+        -> std::vector<SchemaTree::Node::id_t> {
+    if ((type & m_matchable_types) == 0) {
+        return {};
+    }
+
+    switch (type) {
+        case LiteralType::IntegerT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Int);
+        case LiteralType::FloatT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Float);
+        case LiteralType::BooleanT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Bool);
+        case LiteralType::VarStringT:
+        case LiteralType::ClpStringT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Str);
+        default:
+            return {};
+    }
+}
+
+auto ColumnQueryPossibleMatches::get_matchable_node_ids_from_schema_tree_type(
+        SchemaTree::Node::Type type
+) const -> std::vector<SchemaTree::Node::id_t> {
+    std::vector<SchemaTree::Node::id_t> node_ids;
+    for (auto const node_id : m_matchable_node_ids) {
+        if (m_schema_tree->get_node(node_id).get_type() == type) {
+            node_ids.emplace_back(node_id);
+        }
+    }
+    return node_ids;
+}
+
+auto ColumnQueryPossibleMatches::get_projectable_node_ids() const
+        -> std::vector<SchemaTree::Node::id_t> {
+    std::vector<SchemaTree::Node::id_t> non_object_node_ids;
+    for (auto const node_id : m_matchable_node_ids) {
+        if (m_schema_tree->get_node(node_id).get_type() == SchemaTree::Node::Type::Obj) {
+            non_object_node_ids.emplace_back(node_id);
+        }
+    }
+    return non_object_node_ids;
+}
+
+auto ColumnQueryPossibleMatches::serialize() const -> std::string {
+    return fmt::format(
+            "Types: {}; Node IDs: {{{}}}",
+            std::bitset<sizeof(m_matchable_types) * 8>(m_matchable_types).to_string(),
+            fmt::join(m_matchable_node_ids, ",")
+    );
+}
path: components/core/src/clp/ffi/ir_stream/search/test/utils.cpp
user: login: coderabbitai[bot]
body: @LinZhihao-723 You're absolutely right. I reviewed the original code again and see that the braces are indeed correctly balanced. Your format string `"Types: {}; Node IDs: {{{}}}"` properly escapes the literal braces while maintaining the placeholder for `fmt::join`. Since it compiles and works as expected, there's no need for the suggested change. Thanks for the clarification!


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@LinZhihao-723 You're absolutely right. I reviewed the original code again and see that the braces are indeed correctly balanced. Your format string `"Types: {}; Node IDs: {{{}}}"` properly escapes the literal braces while maintaining the placeholder for `fmt::join`. Since it compiles and works as expected, there's no need for the suggested change. Thanks for the clarification!</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T22:15:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,161 @@
+#include "utils.hpp"
+
+#include <bitset>
+#include <map>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../SchemaTree.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::search::ast::LiteralType;
+}  // namespace
+
+auto ColumnQueryPossibleMatches::get_matchable_node_ids_from_literal_type(LiteralType type) const
+        -> std::vector<SchemaTree::Node::id_t> {
+    if ((type & m_matchable_types) == 0) {
+        return {};
+    }
+
+    switch (type) {
+        case LiteralType::IntegerT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Int);
+        case LiteralType::FloatT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Float);
+        case LiteralType::BooleanT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Bool);
+        case LiteralType::VarStringT:
+        case LiteralType::ClpStringT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Str);
+        default:
+            return {};
+    }
+}
+
+auto ColumnQueryPossibleMatches::get_matchable_node_ids_from_schema_tree_type(
+        SchemaTree::Node::Type type
+) const -> std::vector<SchemaTree::Node::id_t> {
+    std::vector<SchemaTree::Node::id_t> node_ids;
+    for (auto const node_id : m_matchable_node_ids) {
+        if (m_schema_tree->get_node(node_id).get_type() == type) {
+            node_ids.emplace_back(node_id);
+        }
+    }
+    return node_ids;
+}
+
+auto ColumnQueryPossibleMatches::get_projectable_node_ids() const
+        -> std::vector<SchemaTree::Node::id_t> {
+    std::vector<SchemaTree::Node::id_t> non_object_node_ids;
+    for (auto const node_id : m_matchable_node_ids) {
+        if (m_schema_tree->get_node(node_id).get_type() == SchemaTree::Node::Type::Obj) {
+            non_object_node_ids.emplace_back(node_id);
+        }
+    }
+    return non_object_node_ids;
path: components/core/src/clp/ffi/ir_stream/search/test/utils.cpp
user: login: LinZhihao-723
body: Right. Fixed in the coming commit.
created_at: 2025-05-02T22:23:34Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,161 @@
+#include "utils.hpp"
+
+#include <bitset>
+#include <map>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../SchemaTree.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::search::ast::LiteralType;
+}  // namespace
+
+auto ColumnQueryPossibleMatches::get_matchable_node_ids_from_literal_type(LiteralType type) const
+        -> std::vector<SchemaTree::Node::id_t> {
+    if ((type & m_matchable_types) == 0) {
+        return {};
+    }
+
+    switch (type) {
+        case LiteralType::IntegerT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Int);
+        case LiteralType::FloatT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Float);
+        case LiteralType::BooleanT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Bool);
+        case LiteralType::VarStringT:
+        case LiteralType::ClpStringT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Str);
+        default:
+            return {};
+    }
+}
+
+auto ColumnQueryPossibleMatches::get_matchable_node_ids_from_schema_tree_type(
+        SchemaTree::Node::Type type
+) const -> std::vector<SchemaTree::Node::id_t> {
+    std::vector<SchemaTree::Node::id_t> node_ids;
+    for (auto const node_id : m_matchable_node_ids) {
+        if (m_schema_tree->get_node(node_id).get_type() == type) {
+            node_ids.emplace_back(node_id);
+        }
+    }
+    return node_ids;
+}
+
+auto ColumnQueryPossibleMatches::get_projectable_node_ids() const
+        -> std::vector<SchemaTree::Node::id_t> {
+    std::vector<SchemaTree::Node::id_t> non_object_node_ids;
+    for (auto const node_id : m_matchable_node_ids) {
+        if (m_schema_tree->get_node(node_id).get_type() == SchemaTree::Node::Type::Obj) {
+            non_object_node_ids.emplace_back(node_id);
+        }
+    }
+    return non_object_node_ids;
path: components/core/src/clp/ffi/ir_stream/search/test/utils.cpp
user: login: coderabbitai[bot]
body: @LinZhihao-723 Great! I'll look forward to seeing the fix in your upcoming commit.


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@LinZhihao-723 Great! I'll look forward to seeing the fix in your upcoming commit.</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T22:23:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,501 @@
+#include <algorithm>
+#include <concepts>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <catch2/catch.hpp>
+
+#include "../../../../../clp_s/search/ast/BooleanLiteral.hpp"
+#include "../../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../../clp_s/search/ast/FilterOperation.hpp"
+#include "../../../../../clp_s/search/ast/Integral.hpp"
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../../clp_s/search/ast/StringLiteral.hpp"
+#include "../../../../ir/EncodedTextAst.hpp"
+#include "../../../../ir/types.hpp"
+#include "../../../encoding_methods.hpp"
+#include "../../../Value.hpp"
+#include "../utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp::ffi::ir_stream::search::evaluate_filter_against_literal_type_value_pair;
+using clp::ffi::Value;
+using clp::ffi::value_bool_t;
+using clp::ffi::value_float_t;
+using clp::ffi::value_int_t;
+using clp::ir::eight_byte_encoded_variable_t;
+using clp::ir::EightByteEncodedTextAst;
+using clp::ir::four_byte_encoded_variable_t;
+using clp::ir::FourByteEncodedTextAst;
+using clp_s::search::ast::BooleanLiteral;
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::FilterOperation;
+using clp_s::search::ast::Integral;
+using clp_s::search::ast::Literal;
+using clp_s::search::ast::LiteralType;
+using clp_s::search::ast::StringLiteral;
+
+using ValueToMatchedFilterOpsPair
+        = std::pair<std::optional<Value>, std::unordered_set<FilterOperation>>;
+
+constexpr std::string_view cRefTestString{"test"};
+
+/**
+ * Parses and encodes the given string as an instance of `EncodedTextAst`.
+ * @tparam encoded_variable_t
+ * @param text
+ * @return The encoded result.
+ */
+template <typename encoded_variable_t>
+requires std::is_same_v<encoded_variable_t, eight_byte_encoded_variable_t>
+         || std::is_same_v<encoded_variable_t, four_byte_encoded_variable_t>
+[[nodiscard]] auto get_encoded_text_ast(std::string_view text)
+        -> clp::ir::EncodedTextAst<encoded_variable_t>;
+
+/**
+ * Asserts the filter evaluation results match the expectation on the given values.
+ * @param filter_to_test
+ * @param filter_operand_literal_type
+ * @param value_to_matched_filter_ops_pairs A vector of pairs, where each pair consists of:
+ *   - A value to test against the filter.
+ *   - All of the filter operations that are expected to evaluate to `true` for this value.
+ * @return Whether all evaluations match their expected results.
+ */
+[[nodiscard]] auto assert_filter_evaluation_results_on_values(
+        FilterExpr const* filter_to_test,
+        LiteralType filter_operand_literal_type,
+        std::vector<ValueToMatchedFilterOpsPair> const& value_to_matched_filter_ops_pairs
+) -> bool;
+
+/**
+ * Checks filter evaluation against integral values.
+ * @tparam IntegralValueType
+ * @param filter_to_test
+ * @param filter_operand_literal_type
+ * @return Whether all evaluations match their expected results.
+ */
+template <typename IntegralValueType>
+requires std::same_as<IntegralValueType, value_int_t>
+         || std::same_as<IntegralValueType, value_float_t>
+[[nodiscard]] auto check_filter_evaluation_against_integral_values(
+        FilterExpr const* filter_to_test,
+        LiteralType filter_operand_literal_type
+) -> bool;
+
+/**
+ * Checks filter evaluation against bool values.
+ * @param filter_to_test
+ * @param filter_operand_literal_type
+ * @return Whether all evaluations match their expected results.
+ */
+[[nodiscard]] auto check_filter_evaluation_against_bool_values(
+        FilterExpr const* filter_to_test,
+        LiteralType filter_operand_literal_type
+) -> bool;
+
+/**
+ * Checks filter evaluation against and variable strings.
+ * @param filter_to_test
+ * @param filter_operand_literal_type
+ * @return Whether all evaluations match their expected results.
+ */
+[[nodiscard]] auto check_filter_evaluation_against_var_strings(
+        FilterExpr const* filter_to_test,
+        LiteralType filter_operand_literal_type
+) -> bool;
+
+/**
+ * Checks filter evaluation against and encoded text ASTs (Clp strings).
+ * @tparam encoded_variable_t
+ * @param filter_to_test
+ * @param filter_operand_literal_type
+ * @return Whether the filter evaluation results match expectation.
+ */
+template <typename encoded_variable_t>
+requires std::is_same_v<encoded_variable_t, eight_byte_encoded_variable_t>
+         || std::is_same_v<encoded_variable_t, four_byte_encoded_variable_t>
+[[nodiscard]] auto check_filter_evaluation_against_encoded_text_asts(
+        FilterExpr const* filter_to_test,
+        LiteralType filter_operand_literal_type
+) -> bool;
+
+/**
+ * Checks filter evaluation against all supported values.
+ * @param filter_to_test
+ * @param filter_operand_literal_type
+ * @return Whether all evaluations match their expected results.
+ */
+[[nodiscard]] auto
+check_filter_evaluation(FilterExpr const* filter_to_test, LiteralType filter_operand_literal_type)
+        -> bool;
+
+template <typename encoded_variable_t>
+requires std::is_same_v<encoded_variable_t, eight_byte_encoded_variable_t>
+         || std::is_same_v<encoded_variable_t, four_byte_encoded_variable_t>
+auto get_encoded_text_ast(std::string_view text) -> clp::ir::EncodedTextAst<encoded_variable_t> {
+    std::string logtype;
+    std::vector<encoded_variable_t> encoded_vars;
+    std::vector<int32_t> dict_var_bounds;
+    REQUIRE(clp::ffi::encode_message(text, logtype, encoded_vars, dict_var_bounds));
+    REQUIRE(((dict_var_bounds.size() % 2) == 0));
+
+    std::vector<std::string> dict_vars;
+    for (size_t i{0}; i < dict_var_bounds.size(); i += 2) {
+        auto const begin_pos{static_cast<size_t>(dict_var_bounds[i])};
+        auto const end_pos{static_cast<size_t>(dict_var_bounds[i + 1])};
+        dict_vars.emplace_back(text.cbegin() + begin_pos, text.cbegin() + end_pos);
+    }
+
+    return clp::ir::EncodedTextAst<encoded_variable_t>{logtype, dict_vars, encoded_vars};
+}
path: components/core/src/clp/ffi/ir_stream/search/test/test_utilis.cpp
user: login: LinZhihao-723
body: No longer related to this PR.
created_at: 2025-05-02T22:24:12Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,501 @@
+#include <algorithm>
+#include <concepts>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <catch2/catch.hpp>
+
+#include "../../../../../clp_s/search/ast/BooleanLiteral.hpp"
+#include "../../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../../clp_s/search/ast/FilterOperation.hpp"
+#include "../../../../../clp_s/search/ast/Integral.hpp"
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../../clp_s/search/ast/StringLiteral.hpp"
+#include "../../../../ir/EncodedTextAst.hpp"
+#include "../../../../ir/types.hpp"
+#include "../../../encoding_methods.hpp"
+#include "../../../Value.hpp"
+#include "../utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp::ffi::ir_stream::search::evaluate_filter_against_literal_type_value_pair;
+using clp::ffi::Value;
+using clp::ffi::value_bool_t;
+using clp::ffi::value_float_t;
+using clp::ffi::value_int_t;
+using clp::ir::eight_byte_encoded_variable_t;
+using clp::ir::EightByteEncodedTextAst;
+using clp::ir::four_byte_encoded_variable_t;
+using clp::ir::FourByteEncodedTextAst;
+using clp_s::search::ast::BooleanLiteral;
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::FilterOperation;
+using clp_s::search::ast::Integral;
+using clp_s::search::ast::Literal;
+using clp_s::search::ast::LiteralType;
+using clp_s::search::ast::StringLiteral;
+
+using ValueToMatchedFilterOpsPair
+        = std::pair<std::optional<Value>, std::unordered_set<FilterOperation>>;
+
+constexpr std::string_view cRefTestString{"test"};
+
+/**
+ * Parses and encodes the given string as an instance of `EncodedTextAst`.
+ * @tparam encoded_variable_t
+ * @param text
+ * @return The encoded result.
+ */
+template <typename encoded_variable_t>
+requires std::is_same_v<encoded_variable_t, eight_byte_encoded_variable_t>
+         || std::is_same_v<encoded_variable_t, four_byte_encoded_variable_t>
+[[nodiscard]] auto get_encoded_text_ast(std::string_view text)
+        -> clp::ir::EncodedTextAst<encoded_variable_t>;
+
+/**
+ * Asserts the filter evaluation results match the expectation on the given values.
+ * @param filter_to_test
+ * @param filter_operand_literal_type
+ * @param value_to_matched_filter_ops_pairs A vector of pairs, where each pair consists of:
+ *   - A value to test against the filter.
+ *   - All of the filter operations that are expected to evaluate to `true` for this value.
+ * @return Whether all evaluations match their expected results.
+ */
+[[nodiscard]] auto assert_filter_evaluation_results_on_values(
+        FilterExpr const* filter_to_test,
+        LiteralType filter_operand_literal_type,
+        std::vector<ValueToMatchedFilterOpsPair> const& value_to_matched_filter_ops_pairs
+) -> bool;
+
+/**
+ * Checks filter evaluation against integral values.
+ * @tparam IntegralValueType
+ * @param filter_to_test
+ * @param filter_operand_literal_type
+ * @return Whether all evaluations match their expected results.
+ */
+template <typename IntegralValueType>
+requires std::same_as<IntegralValueType, value_int_t>
+         || std::same_as<IntegralValueType, value_float_t>
+[[nodiscard]] auto check_filter_evaluation_against_integral_values(
+        FilterExpr const* filter_to_test,
+        LiteralType filter_operand_literal_type
+) -> bool;
+
+/**
+ * Checks filter evaluation against bool values.
+ * @param filter_to_test
+ * @param filter_operand_literal_type
+ * @return Whether all evaluations match their expected results.
+ */
+[[nodiscard]] auto check_filter_evaluation_against_bool_values(
+        FilterExpr const* filter_to_test,
+        LiteralType filter_operand_literal_type
+) -> bool;
+
+/**
+ * Checks filter evaluation against and variable strings.
+ * @param filter_to_test
+ * @param filter_operand_literal_type
+ * @return Whether all evaluations match their expected results.
+ */
+[[nodiscard]] auto check_filter_evaluation_against_var_strings(
+        FilterExpr const* filter_to_test,
+        LiteralType filter_operand_literal_type
+) -> bool;
+
+/**
+ * Checks filter evaluation against and encoded text ASTs (Clp strings).
+ * @tparam encoded_variable_t
+ * @param filter_to_test
+ * @param filter_operand_literal_type
+ * @return Whether the filter evaluation results match expectation.
+ */
+template <typename encoded_variable_t>
+requires std::is_same_v<encoded_variable_t, eight_byte_encoded_variable_t>
+         || std::is_same_v<encoded_variable_t, four_byte_encoded_variable_t>
+[[nodiscard]] auto check_filter_evaluation_against_encoded_text_asts(
+        FilterExpr const* filter_to_test,
+        LiteralType filter_operand_literal_type
+) -> bool;
+
+/**
+ * Checks filter evaluation against all supported values.
+ * @param filter_to_test
+ * @param filter_operand_literal_type
+ * @return Whether all evaluations match their expected results.
+ */
+[[nodiscard]] auto
+check_filter_evaluation(FilterExpr const* filter_to_test, LiteralType filter_operand_literal_type)
+        -> bool;
+
+template <typename encoded_variable_t>
+requires std::is_same_v<encoded_variable_t, eight_byte_encoded_variable_t>
+         || std::is_same_v<encoded_variable_t, four_byte_encoded_variable_t>
+auto get_encoded_text_ast(std::string_view text) -> clp::ir::EncodedTextAst<encoded_variable_t> {
+    std::string logtype;
+    std::vector<encoded_variable_t> encoded_vars;
+    std::vector<int32_t> dict_var_bounds;
+    REQUIRE(clp::ffi::encode_message(text, logtype, encoded_vars, dict_var_bounds));
+    REQUIRE(((dict_var_bounds.size() % 2) == 0));
+
+    std::vector<std::string> dict_vars;
+    for (size_t i{0}; i < dict_var_bounds.size(); i += 2) {
+        auto const begin_pos{static_cast<size_t>(dict_var_bounds[i])};
+        auto const end_pos{static_cast<size_t>(dict_var_bounds[i + 1])};
+        dict_vars.emplace_back(text.cbegin() + begin_pos, text.cbegin() + end_pos);
+    }
+
+    return clp::ir::EncodedTextAst<encoded_variable_t>{logtype, dict_vars, encoded_vars};
+}
path: components/core/src/clp/ffi/ir_stream/search/test/test_utilis.cpp
user: login: coderabbitai[bot]
body: @LinZhihao-723 I understand. Thank you for letting me know that this suggestion is no longer relevant to the current PR.


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@LinZhihao-723 I understand. Thank you for letting me know that this suggestion is no longer relevant to the current PR.</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T22:24:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,419 @@
+#include <map>
+#include <memory>
+#include <set>
+#include <sstream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <catch2/catch.hpp>
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/archive_constants.hpp"
+#include "../../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../../clp_s/search/kql/kql.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../../../Value.hpp"
+#include "../QueryHandlerImpl.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::search::ast::LiteralTypeBitmask;
+
+constexpr std::string_view cRefTestStr{"*test*"};
+constexpr value_int_t cRefTestInt{0};
+constexpr value_float_t cRefTestFloat{0.0};
+constexpr value_bool_t cRefTestBool{false};
+
+/**
+ * Generates all the matchable KQL expressions based on the column queries and their matchable
+ * types. For each column query `C` matched type `T`, we generate an expression of `C: cRefTestT`,
+ * where `cRefTestT` is any of the constants defined above.
+ * @param is_auto_generated Whether the query is in auto-generated namespace.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - A vector of matchable KQL expressions.
+ * - A map of expected column resolutions.
+ */
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        bool is_auto_generated,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param is_auto_generated Whether the query is in auto-generated namespace.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - Generated projections as pairs, where each pair consists of:
+ *   - A projectable column from `column_query_to_possible_matches` that has no wildcard.
+ *   - All the matchable types of this column.
+ * - A map of expected resolved projections.
+ */
+[[nodiscard]] auto generate_projections(
+        bool is_auto_generated,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+)
+        -> std::pair<
+                std::vector<std::pair<std::string, LiteralTypeBitmask>>,
+                std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param column_resolutions
+ * @return The serialized pairs.
+ */
+[[nodiscard]] auto serialize_column_node_ids_map(
+        std::map<std::string, std::set<SchemaTree::Node::id_t>> const& column_resolutions
+) -> std::string;
+
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        bool is_auto_generated,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>> {
+    std::vector<std::string> matchable_kql_expressions;
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> expected_column_resolutions;
+    for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
+        auto const column_query_with_namespace{fmt::format(
+                "{}{}",
+                is_auto_generated ? clp_s::constants::cAutogenNamespace
+                                  : clp_s::constants::cDefaultNamespace,
+                column_query
+        )};
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Int
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestInt)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Float
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {:.2f}", column_query_with_namespace, cRefTestFloat)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Bool
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestBool)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Str
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestStr)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+    }
+
+    if (expected_column_resolutions.contains("*")) {
+        // NOTE: The current implementation doesn't resolve single wildcard.
+        expected_column_resolutions.erase("*");
+    }
+    return {std::move(matchable_kql_expressions), std::move(expected_column_resolutions)};
+}
+
+auto generate_projections(
+        bool is_auto_generated,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+)
+        -> std::pair<
+                std::vector<std::pair<std::string, LiteralTypeBitmask>>,
+                std::map<std::string, std::set<SchemaTree::Node::id_t>>> {
+    std::vector<std::pair<std::string, LiteralTypeBitmask>> projections;
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> expected_resolved_projections;
+    for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
+        if (column_query.find('*') != std::string::npos) {
+            continue;
+        }
+        auto const projectable_node_ids{possible_matches.get_projectable_node_ids()};
+        if (projections.empty()) {
+            continue;
+        }
path: components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp
user: login: LinZhihao-723
body: Fixed in the coming commit.
created_at: 2025-05-02T22:25:22Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,419 @@
+#include <map>
+#include <memory>
+#include <set>
+#include <sstream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <catch2/catch.hpp>
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/archive_constants.hpp"
+#include "../../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../../clp_s/search/kql/kql.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../../../Value.hpp"
+#include "../QueryHandlerImpl.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::search::ast::LiteralTypeBitmask;
+
+constexpr std::string_view cRefTestStr{"*test*"};
+constexpr value_int_t cRefTestInt{0};
+constexpr value_float_t cRefTestFloat{0.0};
+constexpr value_bool_t cRefTestBool{false};
+
+/**
+ * Generates all the matchable KQL expressions based on the column queries and their matchable
+ * types. For each column query `C` matched type `T`, we generate an expression of `C: cRefTestT`,
+ * where `cRefTestT` is any of the constants defined above.
+ * @param is_auto_generated Whether the query is in auto-generated namespace.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - A vector of matchable KQL expressions.
+ * - A map of expected column resolutions.
+ */
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        bool is_auto_generated,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param is_auto_generated Whether the query is in auto-generated namespace.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - Generated projections as pairs, where each pair consists of:
+ *   - A projectable column from `column_query_to_possible_matches` that has no wildcard.
+ *   - All the matchable types of this column.
+ * - A map of expected resolved projections.
+ */
+[[nodiscard]] auto generate_projections(
+        bool is_auto_generated,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+)
+        -> std::pair<
+                std::vector<std::pair<std::string, LiteralTypeBitmask>>,
+                std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param column_resolutions
+ * @return The serialized pairs.
+ */
+[[nodiscard]] auto serialize_column_node_ids_map(
+        std::map<std::string, std::set<SchemaTree::Node::id_t>> const& column_resolutions
+) -> std::string;
+
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        bool is_auto_generated,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>> {
+    std::vector<std::string> matchable_kql_expressions;
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> expected_column_resolutions;
+    for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
+        auto const column_query_with_namespace{fmt::format(
+                "{}{}",
+                is_auto_generated ? clp_s::constants::cAutogenNamespace
+                                  : clp_s::constants::cDefaultNamespace,
+                column_query
+        )};
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Int
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestInt)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Float
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {:.2f}", column_query_with_namespace, cRefTestFloat)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Bool
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestBool)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Str
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestStr)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+    }
+
+    if (expected_column_resolutions.contains("*")) {
+        // NOTE: The current implementation doesn't resolve single wildcard.
+        expected_column_resolutions.erase("*");
+    }
+    return {std::move(matchable_kql_expressions), std::move(expected_column_resolutions)};
+}
+
+auto generate_projections(
+        bool is_auto_generated,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+)
+        -> std::pair<
+                std::vector<std::pair<std::string, LiteralTypeBitmask>>,
+                std::map<std::string, std::set<SchemaTree::Node::id_t>>> {
+    std::vector<std::pair<std::string, LiteralTypeBitmask>> projections;
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> expected_resolved_projections;
+    for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
+        if (column_query.find('*') != std::string::npos) {
+            continue;
+        }
+        auto const projectable_node_ids{possible_matches.get_projectable_node_ids()};
+        if (projections.empty()) {
+            continue;
+        }
path: components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: @LinZhihao-723 Great! Thank you for addressing this issue. I'll look forward to seeing the fix in the upcoming commit.


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@LinZhihao-723 Great! Thank you for addressing this issue. I'll look forward to seeing the fix in the upcoming commit.</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T22:25:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -457,7 +457,12 @@ set(SOURCE_FILES_unitTest
         src/clp/ffi/ir_stream/search/ErrorCode.hpp
         src/clp/ffi/ir_stream/search/NewProjectedSchemaTreeNodeCallbackReq.hpp
         src/clp/ffi/ir_stream/search/QueryHandler.hpp
+        src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
+        src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
+        src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp
         src/clp/ffi/ir_stream/search/test/test_utils.cpp
+        src/clp/ffi/ir_stream/search/test/utils.cpp
+        src/clp/ffi/ir_stream/search/test/utils.hpp
path: components/core/CMakeLists.txt
user: login: LinZhihao-723
body: Oh, we think this duplication is fine. Test cases are marked with `test_` prefix and located in the `test` directory anyways. They won't be in the same namespace level.
created_at: 2025-05-02T22:26:18Z
author_association: MEMBER
diff_hunk: @@ -457,7 +457,12 @@ set(SOURCE_FILES_unitTest
         src/clp/ffi/ir_stream/search/ErrorCode.hpp
         src/clp/ffi/ir_stream/search/NewProjectedSchemaTreeNodeCallbackReq.hpp
         src/clp/ffi/ir_stream/search/QueryHandler.hpp
+        src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
+        src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
+        src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp
         src/clp/ffi/ir_stream/search/test/test_utils.cpp
+        src/clp/ffi/ir_stream/search/test/utils.cpp
+        src/clp/ffi/ir_stream/search/test/utils.hpp
path: components/core/CMakeLists.txt
user: login: coderabbitai[bot]
body: @LinZhihao-723 That's a fair point. If your team is comfortable with this naming convention, that's completely fine. The separation by directory structure and namespace should indeed be sufficient to avoid practical issues. Thanks for clarifying!


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@LinZhihao-723 That's a fair point. If your team is comfortable with this naming convention, that's completely fine. The separation by directory structure and namespace should indeed be sufficient to avoid practical issues. Thanks for clarifying!</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T22:26:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,324 @@
+#include "QueryHandlerImpl.hpp"
+
+#include <exception>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/archive_constants.hpp"
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/ConvertToExists.hpp"
+#include "../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../clp_s/search/ast/NarrowTypes.hpp"
+#include "../../../../clp_s/search/ast/OrOfAndForm.hpp"
+#include "../../../../clp_s/search/ast/SearchUtils.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::EmptyExpr;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Pre-processes a search query by applying several transformation passes.
+ * @param query
+ * @return A result containing the transformed
+ * - ErrorCodeEnum::QueryExpressionIsNull if `query` is nullptr.
+ * - ErrorCodeEnum::QueryTransformationPassFailed if any of the transformation pass failed.
+ */
+[[nodiscard]] auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>>;
+
+/**
+ * Creates projected columns and column-to-original-key map from the given projections.
+ * @param projections
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - A vector of projected columns.
+ *   - A projected-column-to-original-key map.
+ * - The possible error codes:
+ *   - ErrorCodeEnum::DuplicateProjectedColumn if `projections` contains duplicated keys.
+ *   - ErrorCodeEnum::ColumnTokenizationFailure if failed to tokenize a projection key.
+ *   - ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure if failed to create a column
+ *     descriptor for the projected key.
+ */
+[[nodiscard]] auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>>;
+
+/**
+ * Creates initial partial resolutions for the given query and projections.
+ * @param query
+ * @param projected_column_to_original_key
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - The partial resolution for auto-generated keys.
+ *   - The partial resolution for user-generated keys.
+ * - The possible error codes:
+ *   - Forwards `is_auto_generated`'s return values.
+ *   - Forwards `initialize_partial_resolution_from_search_ast`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::create`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::next`'s return values.
+ */
+[[nodiscard]] auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>>;
+
+/**
+ * Initializes partial resolutions from a search AST.
+ * @param root The root of the search AST.
+ * @param auto_gen_namespace_partial_resolutions Returns initialized auto-gen partial resolutions.
+ * @param user_gen_namespace_partial_resolutions Returns initialized user-gen partial resolutions.
+ * @return A void result on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::AstDynamicCastFailure if failed to dynamically cast an AST node to a target
+ *   type.
+ * - Forwards `is_auto_generated`'s return values.
+ */
+[[nodiscard]] auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void>;
+
+/**
+ * @param key_namespace
+ * @return A result containing a boolean indicating whether `key_namespace` is auto-generated or
+ * user-generated on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::UnsupportedNamespace if the namespace is not supported.
+ */
+[[nodiscard]] auto is_auto_generated(std::string_view key_namespace)
+        -> outcome_v2::std_result<bool>;
+
+auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>> {
+    if (nullptr == query) {
+        return ErrorCode{ErrorCodeEnum::QueryExpressionIsNull};
+    }
+
+    if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(query)) {
+        return query;
+    }
+
+    try {
+        if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        if (query = clp_s::search::ast::NarrowTypes{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        return clp_s::search::ast::ConvertToExists{}.run(query);
+    } catch (std::exception const& ex) {
+        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+    }
+}
+
+auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>> {
+    std::unordered_set<std::string_view> unique_projected_columns;
+    std::vector<std::shared_ptr<ColumnDescriptor>> projected_columns;
+    QueryHandlerImpl::ProjectionMap projected_column_to_original_key;
+
+    for (auto const& [key, types] : projections) {
+        if (unique_projected_columns.contains(key)) {
+            return ErrorCode{ErrorCodeEnum::DuplicateProjectedColumn};
+        }
+        unique_projected_columns.emplace(key);
+
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
user: login: LinZhihao-723
body: I don't think we will do anything else rather than just using this set to check duplications.
created_at: 2025-05-02T22:27:25Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,324 @@
+#include "QueryHandlerImpl.hpp"
+
+#include <exception>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/archive_constants.hpp"
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/ConvertToExists.hpp"
+#include "../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../clp_s/search/ast/NarrowTypes.hpp"
+#include "../../../../clp_s/search/ast/OrOfAndForm.hpp"
+#include "../../../../clp_s/search/ast/SearchUtils.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::EmptyExpr;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Pre-processes a search query by applying several transformation passes.
+ * @param query
+ * @return A result containing the transformed
+ * - ErrorCodeEnum::QueryExpressionIsNull if `query` is nullptr.
+ * - ErrorCodeEnum::QueryTransformationPassFailed if any of the transformation pass failed.
+ */
+[[nodiscard]] auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>>;
+
+/**
+ * Creates projected columns and column-to-original-key map from the given projections.
+ * @param projections
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - A vector of projected columns.
+ *   - A projected-column-to-original-key map.
+ * - The possible error codes:
+ *   - ErrorCodeEnum::DuplicateProjectedColumn if `projections` contains duplicated keys.
+ *   - ErrorCodeEnum::ColumnTokenizationFailure if failed to tokenize a projection key.
+ *   - ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure if failed to create a column
+ *     descriptor for the projected key.
+ */
+[[nodiscard]] auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>>;
+
+/**
+ * Creates initial partial resolutions for the given query and projections.
+ * @param query
+ * @param projected_column_to_original_key
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - The partial resolution for auto-generated keys.
+ *   - The partial resolution for user-generated keys.
+ * - The possible error codes:
+ *   - Forwards `is_auto_generated`'s return values.
+ *   - Forwards `initialize_partial_resolution_from_search_ast`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::create`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::next`'s return values.
+ */
+[[nodiscard]] auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>>;
+
+/**
+ * Initializes partial resolutions from a search AST.
+ * @param root The root of the search AST.
+ * @param auto_gen_namespace_partial_resolutions Returns initialized auto-gen partial resolutions.
+ * @param user_gen_namespace_partial_resolutions Returns initialized user-gen partial resolutions.
+ * @return A void result on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::AstDynamicCastFailure if failed to dynamically cast an AST node to a target
+ *   type.
+ * - Forwards `is_auto_generated`'s return values.
+ */
+[[nodiscard]] auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void>;
+
+/**
+ * @param key_namespace
+ * @return A result containing a boolean indicating whether `key_namespace` is auto-generated or
+ * user-generated on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::UnsupportedNamespace if the namespace is not supported.
+ */
+[[nodiscard]] auto is_auto_generated(std::string_view key_namespace)
+        -> outcome_v2::std_result<bool>;
+
+auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>> {
+    if (nullptr == query) {
+        return ErrorCode{ErrorCodeEnum::QueryExpressionIsNull};
+    }
+
+    if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(query)) {
+        return query;
+    }
+
+    try {
+        if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        if (query = clp_s::search::ast::NarrowTypes{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        return clp_s::search::ast::ConvertToExists{}.run(query);
+    } catch (std::exception const& ex) {
+        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+    }
+}
+
+auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>> {
+    std::unordered_set<std::string_view> unique_projected_columns;
+    std::vector<std::shared_ptr<ColumnDescriptor>> projected_columns;
+    QueryHandlerImpl::ProjectionMap projected_column_to_original_key;
+
+    for (auto const& [key, types] : projections) {
+        if (unique_projected_columns.contains(key)) {
+            return ErrorCode{ErrorCodeEnum::DuplicateProjectedColumn};
+        }
+        unique_projected_columns.emplace(key);
+
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: @LinZhihao-723 You're right - since the set is only used locally for duplicate checking and doesn't outlive the function, the current implementation with `std::string_view` is safe. The original strings will remain valid throughout the set's lifetime.



ʕ •ᴥ•ʔ

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: LinZhihao-723
PR: y-scope/clp#873
File: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp:148-157
Timestamp: 2025-05-02T22:27:59.339Z
Learning: In the `QueryHandlerImpl.cpp` file, the `unique_projected_columns` set (using `std::string_view`) is intentionally designed to only check for duplications within the local scope of the `create_projected_columns_and_projection_map` function. The team decided this is an acceptable use of `std::string_view` in a container since the referenced strings remain valid throughout the function's execution.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>




<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@LinZhihao-723 You're right - since the set is only used locally for duplicate checking and doesn't outlive the function, the current implementation with `std::string_view` is safe. The original strings will remain valid throughout the set's lifetime.



ʕ •ᴥ•ʔ

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: LinZhihao-723
PR: y-scope/clp#873
File: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp:148-157
Timestamp: 2025-05-02T22:27:59.339Z
Learning: In the `QueryHandlerImpl.cpp` file, the `unique_projected_columns` set (using `std::string_view`) is intentionally designed to only check for duplications within the local scope of the `create_projected_columns_and_projection_map` function. The team decided this is an acceptable use of `std::string_view` in a container since the referenced strings remain valid throughout the function's execution.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>

</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T22:28:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,349 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param colum_descriptor
+         * @return A result containing the constructed token iterator on success, or an error code
+         * indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the column doesn't have any
+         *   token.
+         */
+        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* colum_descriptor)
+                -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            auto const token_begin_it{colum_descriptor->descriptor_begin()};
+            if (colum_descriptor->descriptor_end() == token_begin_it) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound};
+            }
+            return ColumnDescriptorTokenIterator{colum_descriptor, token_begin_it};
+        }
+
+        // Methods
+        /**
+         * @return Whether this is the last token of the underlying column descriptor.
+         */
+        [[nodiscard]] auto is_last() const -> bool {
+            return m_column_descriptor->descriptor_end() == m_next_token_it;
+        }
+
+        /**
+         * @return A result containing a newly constructor iterator pointing to the next token on
+         * success, or an error code indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the current token is already
+         *   the last one.
+         */
+        [[nodiscard]] auto next() const -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            if (is_last()) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound};
+            }
+            return ColumnDescriptorTokenIterator{m_column_descriptor, m_next_token_it};
+        }
+
+        [[nodiscard]] auto is_wildcard() const -> bool { return m_curr_token_it->wildcard(); }
+
+        [[nodiscard]] auto get_token() const -> std::string_view {
+            return m_curr_token_it->get_token();
+        }
+
+        [[nodiscard]] auto get_column_descriptor() const -> clp_s::search::ast::ColumnDescriptor* {
+            return m_column_descriptor;
+        }
+
+        /**
+         * @param type
+         * @return Whether the underlying column can match the given schema-tree node type.
+         */
+        [[nodiscard]] auto match(SchemaTree::Node::Type type) const -> bool {
+            return m_column_descriptor->matches_any(schema_tree_node_type_to_literal_types(type));
+        }
+
+    private:
+        // Constructor
+        ColumnDescriptorTokenIterator(
+                clp_s::search::ast::ColumnDescriptor* colum_descriptor,
+                clp_s::search::ast::DescriptorList::iterator curr_token_it
+        )
+                : m_column_descriptor{colum_descriptor},
+                  m_curr_token_it{curr_token_it},
+                  m_next_token_it{curr_token_it + 1} {}
+
+        clp_s::search::ast::ColumnDescriptor* m_column_descriptor;
+        clp_s::search::ast::DescriptorList::iterator m_curr_token_it;
+        clp_s::search::ast::DescriptorList::iterator m_next_token_it;
+    };
+
+    using ProjectionMap = std::unordered_map<clp_s::search::ast::ColumnDescriptor*, std::string>;
+
+    using PartialResolutionMap = std::
+            unordered_map<SchemaTree::Node::id_t, std::vector<ColumnDescriptorTokenIterator>>;
+
+    // Factory function
+    /**
+     * @param query The search query.
+     * @param projections The columns to project.
+     * @param case_sensitive_match Whether to use case-sensitive match for string comparison.
+     * @return A result containing the newly constructed `QueryHandler` on success, or an error code
+     * indicating the failure:
+     * - Forwards `preprocess_query`'s return values.
+     * - Forwards `create_projected_columns_and_projection_map`'s return values.
+     * - Forwards `create_initial_partial_resolutions`'s return values.
+     */
+    [[nodiscard]] static auto create(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            std::vector<std::pair<std::string, clp_s::search::ast::LiteralTypeBitmask>> const&
+                    projections,
+            bool case_sensitive_match
+    ) -> outcome_v2::std_result<QueryHandlerImpl>;
+
+    // Delete copy constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl const&) = delete;
+    auto operator=(QueryHandlerImpl const&) -> QueryHandlerImpl& = delete;
+
+    // Default move constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl&&) = default;
+    auto operator=(QueryHandlerImpl&&) -> QueryHandlerImpl& = default;
+
+    // Destructor
+    ~QueryHandlerImpl() = default;
+
+    /**
+     * Implementation of `QueryHandler::evaluate_node_id_value_pairs`.
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - TODO
+     */
+    [[nodiscard]] auto evaluate_node_id_value_pairs(
+            KeyValuePairLogEvent::NodeIdValuePairs const& auto_gen_node_id_value_pairs,
+            KeyValuePairLogEvent::NodeIdValuePairs const& user_gen_node_id_value_pairs
+    ) -> outcome_v2::std_result<AstEvaluationResult>;
+
+    /**
+     * Implementation of `QueryHandler::update_partially_resolved_columns` with new projected
+     * schema-tree node callback given as a template parameter.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @param new_projected_schema_tree_node_callback
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `handle_column_resolution_on_leaf_node`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto update_partially_resolved_columns(
+            bool is_auto_generated,
+            SchemaTree::NodeLocator const& node_locator,
+            SchemaTree::Node::id_t node_id,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    [[nodiscard]] auto get_resolved_column_to_schema_tree_node_ids() const -> std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>> {
+        return m_resolved_column_to_schema_tree_node_ids;
+    }
+
+private:
+    // Constructor
+    QueryHandlerImpl(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            PartialResolutionMap auto_gen_namespace_partial_resolutions,
+            PartialResolutionMap user_gen_namespace_partial_resolutions,
+            std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> projected_columns,
+            ProjectionMap projected_column_to_original_key,
+            bool case_sensitive_match
+    )
+            : m_query{std::move(query)},
+              m_auto_gen_namespace_partial_resolutions{
+                      std::move(auto_gen_namespace_partial_resolutions)
+              },
+              m_user_gen_namespace_partial_resolutions{
+                      std::move(user_gen_namespace_partial_resolutions)
+              },
+              m_projected_columns{std::move(projected_columns)},
+              m_projected_column_to_original_key{std::move(projected_column_to_original_key)},
+              m_case_sensitive_search{case_sensitive_match} {}
+
+    // Methods
+    /**
+     * Handles column resolution on the given schema-tree leaf node and the token in the column.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param is_auto_generated
+     * @param leaf_node_id
+     * @param leaf_node_locator
+     * @param token_it The current resolved token in the column.
+     * @param new_projected_schema_tree_node_callback
+     * @return A void result on success, or an error code indicating the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `new_projected_schema_tree_node_callback`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto handle_column_resolution_on_leaf_node(
+            bool is_auto_generated,
+            SchemaTree::Node::id_t leaf_node_id,
+            SchemaTree::NodeLocator const& leaf_node_locator,
+            ColumnDescriptorTokenIterator const& token_it,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    // Variables
+    std::shared_ptr<clp_s::search::ast::Expression> m_query;
+    PartialResolutionMap m_auto_gen_namespace_partial_resolutions;
+    PartialResolutionMap m_user_gen_namespace_partial_resolutions;
+    std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>>
+            m_resolved_column_to_schema_tree_node_ids;
+    std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> m_projected_columns;
+    ProjectionMap m_projected_column_to_original_key;
+    bool m_case_sensitive_search;
+};
+
+template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+auto QueryHandlerImpl::update_partially_resolved_columns(
+        bool is_auto_generated,
+        SchemaTree::NodeLocator const& node_locator,
+        SchemaTree::Node::id_t node_id,
+        NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+) -> outcome_v2::std_result<void> {
+    auto const parent_node_id{node_locator.get_parent_id()};
+    auto& partial_resolutions_to_update{
+            is_auto_generated ? m_auto_gen_namespace_partial_resolutions
+                              : m_user_gen_namespace_partial_resolutions
+    };
+    if (false == partial_resolutions_to_update.contains(parent_node_id)) {
+        return outcome_v2::success();
+    }
+
+    for (auto const& token_it : partial_resolutions_to_update.at(parent_node_id)) {
+        auto const next_token_it_result{token_it.next()};
+        if (SchemaTree::Node::Type::Obj == node_locator.get_type()) {
+            // Handle schema-tree non-leaf nodes.
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: LinZhihao-723
body: Fixed.
created_at: 2025-05-02T22:39:26Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,344 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param colum_descriptor
+         * @return A result containing the constructed token iterator on success, or an error code
+         * indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the column doesn't have any
+         *   token.
+         */
+        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* colum_descriptor)
+                -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            auto const token_begin_it{colum_descriptor->descriptor_begin()};
+            if (colum_descriptor->descriptor_end() == token_begin_it) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound};
+            }
+            return ColumnDescriptorTokenIterator{colum_descriptor, token_begin_it};
+        }
+
+        // Methods
+        /**
+         * @return Whether this is the last token of the underlying column descriptor.
+         */
+        [[nodiscard]] auto is_last() const -> bool {
+            return m_column_descriptor->descriptor_end() == m_next_token_it;
+        }
+
+        /**
+         * @return A result containing a newly constructor iterator pointing to the next token on
+         * success, or an error code indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the current token is already
+         *   the last one.
+         */
+        [[nodiscard]] auto next() const -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            if (is_last()) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound};
+            }
+            return ColumnDescriptorTokenIterator{m_column_descriptor, m_next_token_it};
+        }
+
+        [[nodiscard]] auto is_wildcard() const -> bool { return m_curr_token_it->wildcard(); }
+
+        [[nodiscard]] auto get_token() const -> std::string_view {
+            return m_curr_token_it->get_token();
+        }
+
+        [[nodiscard]] auto get_column_descriptor() const -> clp_s::search::ast::ColumnDescriptor* {
+            return m_column_descriptor;
+        }
+
+        /**
+         * @param type
+         * @return Whether the underlying column can match the given schema-tree node type.
+         */
+        [[nodiscard]] auto match(SchemaTree::Node::Type type) const -> bool {
+            return m_column_descriptor->matches_any(schema_tree_node_type_to_literal_types(type));
+        }
+
+    private:
+        // Constructor
+        ColumnDescriptorTokenIterator(
+                clp_s::search::ast::ColumnDescriptor* colum_descriptor,
+                clp_s::search::ast::DescriptorList::iterator curr_token_it
+        )
+                : m_column_descriptor{colum_descriptor},
+                  m_curr_token_it{curr_token_it},
+                  m_next_token_it{curr_token_it + 1} {}
+
+        clp_s::search::ast::ColumnDescriptor* m_column_descriptor;
+        clp_s::search::ast::DescriptorList::iterator m_curr_token_it;
+        clp_s::search::ast::DescriptorList::iterator m_next_token_it;
+    };
+
+    using ProjectionMap = std::unordered_map<clp_s::search::ast::ColumnDescriptor*, std::string>;
+
+    using PartialResolutionMap = std::
+            unordered_map<SchemaTree::Node::id_t, std::vector<ColumnDescriptorTokenIterator>>;
+
+    // Factory function
+    /**
+     * @param query The search query.
+     * @param projections The columns to project.
+     * @param case_sensitive_match Whether to use case-sensitive match for string comparison.
+     * @return A result containing the newly constructed `QueryHandler` on success, or an error code
+     * indicating the failure:
+     * - Forwards `preprocess_query`'s return values.
+     * - Forwards `create_projected_columns_and_projection_map`'s return values.
+     * - Forwards `create_initial_partial_resolutions`'s return values.
+     */
+    [[nodiscard]] static auto create(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            std::vector<std::pair<std::string, clp_s::search::ast::LiteralTypeBitmask>> const&
+                    projections,
+            bool case_sensitive_match
+    ) -> outcome_v2::std_result<QueryHandlerImpl>;
+
+    // Delete copy constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl const&) = delete;
+    auto operator=(QueryHandlerImpl const&) -> QueryHandlerImpl& = delete;
+
+    // Default move constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl&&) = default;
+    auto operator=(QueryHandlerImpl&&) -> QueryHandlerImpl& = default;
+
+    // Destructor
+    ~QueryHandlerImpl() = default;
+
+    /**
+     * Implementation of `QueryHandler::evaluate_node_id_value_pairs`.
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - TODO
+     */
+    [[nodiscard]] auto evaluate_node_id_value_pairs(
+            KeyValuePairLogEvent::NodeIdValuePairs const& auto_gen_node_id_value_pairs,
+            KeyValuePairLogEvent::NodeIdValuePairs const& user_gen_node_id_value_pairs
+    ) -> outcome_v2::std_result<AstEvaluationResult>;
+
+    /**
+     * Implementation of `QueryHandler::update_partially_resolved_columns` with new projected
+     * schema-tree node callback given as a template parameter.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @param new_projected_schema_tree_node_callback
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `handle_column_resolution_on_leaf_node`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto update_partially_resolved_columns(
+            bool is_auto_generated,
+            SchemaTree::NodeLocator const& node_locator,
+            SchemaTree::Node::id_t node_id,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    [[nodiscard]] auto get_resolved_column_to_schema_tree_node_ids() const -> std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::vector<SchemaTree::Node::id_t>> {
+        return m_resolved_column_to_schema_tree_node_ids;
+    }
+
+private:
+    // Constructor
+    QueryHandlerImpl(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            PartialResolutionMap auto_gen_namespace_partial_resolutions,
+            PartialResolutionMap user_gen_namespace_partial_resolutions,
+            std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> projected_columns,
+            ProjectionMap projected_column_to_original_key,
+            bool case_sensitive_match
+    )
+            : m_query{std::move(query)},
+              m_auto_gen_namespace_partial_resolutions{
+                      std::move(auto_gen_namespace_partial_resolutions)
+              },
+              m_user_gen_namespace_partial_resolutions{
+                      std::move(user_gen_namespace_partial_resolutions)
+              },
+              m_projected_columns{std::move(projected_columns)},
+              m_projected_column_to_original_key{std::move(projected_column_to_original_key)},
+              m_case_sensitive_search{case_sensitive_match} {}
+
+    // Methods
+    /**
+     * Handles column resolution on the given schema-tree leaf node and the token in the column.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param is_auto_generated
+     * @param leaf_node_id
+     * @param leaf_node_locator
+     * @param token_it The current resolved token in the column.
+     * @param new_projected_schema_tree_node_callback
+     * @return A void result on success, or an error code indicating the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `new_projected_schema_tree_node_callback`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto handle_column_resolution_on_leaf_node(
+            bool is_auto_generated,
+            SchemaTree::Node::id_t leaf_node_id,
+            SchemaTree::NodeLocator const& leaf_node_locator,
+            ColumnDescriptorTokenIterator const& token_it,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    // Variables
+    std::shared_ptr<clp_s::search::ast::Expression> m_query;
+    PartialResolutionMap m_auto_gen_namespace_partial_resolutions;
+    PartialResolutionMap m_user_gen_namespace_partial_resolutions;
+    std::unordered_map<clp_s::search::ast::ColumnDescriptor*, std::vector<SchemaTree::Node::id_t>>
+            m_resolved_column_to_schema_tree_node_ids;
+    std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> m_projected_columns;
+    ProjectionMap m_projected_column_to_original_key;
+    bool m_case_sensitive_search;
+};
+
+template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+auto QueryHandlerImpl::update_partially_resolved_columns(
+        bool is_auto_generated,
+        SchemaTree::NodeLocator const& node_locator,
+        SchemaTree::Node::id_t node_id,
+        NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+) -> outcome_v2::std_result<void> {
+    auto const parent_node_id{node_locator.get_parent_id()};
+    auto& partial_resolutions_to_update{
+            is_auto_generated ? m_auto_gen_namespace_partial_resolutions
+                              : m_user_gen_namespace_partial_resolutions
+    };
+    if (false == partial_resolutions_to_update.contains(parent_node_id)) {
+        return outcome_v2::success();
+    }
+
+    for (auto const& token_it : partial_resolutions_to_update.at(parent_node_id)) {
+        auto const next_token_it_result{token_it.next()};
+        if (SchemaTree::Node::Type::Obj == node_locator.get_type()) {
+            // Handle schema-tree non-leaf nodes.
+            if (token_it.is_last()) {
+                // TODO: Handle object search when supported.
+                continue;
+            }
+
+            if (token_it.is_wildcard()) {
+                auto [it, inserted] = partial_resolutions_to_update.try_emplace(
+                        node_id,
+                        std::vector<ColumnDescriptorTokenIterator>{}
+                );
+                it->second.emplace_back(token_it);
+                it->second.emplace_back(OUTCOME_TRYX(token_it.next()));
+                continue;
+            }
+
+            if (token_it.get_token() != node_locator.get_key_name()) {
+                continue;
+            }
+
+            auto const next_token_it{OUTCOME_TRYX(token_it.next())};
+            auto [it, inserted] = partial_resolutions_to_update.try_emplace(
+                    node_id,
+                    std::vector<ColumnDescriptorTokenIterator>{}
+            );
+            it->second.emplace_back(next_token_it);
+            if (false == next_token_it.is_last() && next_token_it.is_wildcard()) {
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: LinZhihao-723
body: Fixed.
created_at: 2025-05-02T22:39:37Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,349 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param colum_descriptor
+         * @return A result containing the constructed token iterator on success, or an error code
+         * indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the column doesn't have any
+         *   token.
+         */
+        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* colum_descriptor)
+                -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            auto const token_begin_it{colum_descriptor->descriptor_begin()};
+            if (colum_descriptor->descriptor_end() == token_begin_it) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound};
+            }
+            return ColumnDescriptorTokenIterator{colum_descriptor, token_begin_it};
+        }
+
+        // Methods
+        /**
+         * @return Whether this is the last token of the underlying column descriptor.
+         */
+        [[nodiscard]] auto is_last() const -> bool {
+            return m_column_descriptor->descriptor_end() == m_next_token_it;
+        }
+
+        /**
+         * @return A result containing a newly constructor iterator pointing to the next token on
+         * success, or an error code indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the current token is already
+         *   the last one.
+         */
+        [[nodiscard]] auto next() const -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            if (is_last()) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound};
+            }
+            return ColumnDescriptorTokenIterator{m_column_descriptor, m_next_token_it};
+        }
+
+        [[nodiscard]] auto is_wildcard() const -> bool { return m_curr_token_it->wildcard(); }
+
+        [[nodiscard]] auto get_token() const -> std::string_view {
+            return m_curr_token_it->get_token();
+        }
+
+        [[nodiscard]] auto get_column_descriptor() const -> clp_s::search::ast::ColumnDescriptor* {
+            return m_column_descriptor;
+        }
+
+        /**
+         * @param type
+         * @return Whether the underlying column can match the given schema-tree node type.
+         */
+        [[nodiscard]] auto match(SchemaTree::Node::Type type) const -> bool {
+            return m_column_descriptor->matches_any(schema_tree_node_type_to_literal_types(type));
+        }
+
+    private:
+        // Constructor
+        ColumnDescriptorTokenIterator(
+                clp_s::search::ast::ColumnDescriptor* colum_descriptor,
+                clp_s::search::ast::DescriptorList::iterator curr_token_it
+        )
+                : m_column_descriptor{colum_descriptor},
+                  m_curr_token_it{curr_token_it},
+                  m_next_token_it{curr_token_it + 1} {}
+
+        clp_s::search::ast::ColumnDescriptor* m_column_descriptor;
+        clp_s::search::ast::DescriptorList::iterator m_curr_token_it;
+        clp_s::search::ast::DescriptorList::iterator m_next_token_it;
+    };
+
+    using ProjectionMap = std::unordered_map<clp_s::search::ast::ColumnDescriptor*, std::string>;
+
+    using PartialResolutionMap = std::
+            unordered_map<SchemaTree::Node::id_t, std::vector<ColumnDescriptorTokenIterator>>;
+
+    // Factory function
+    /**
+     * @param query The search query.
+     * @param projections The columns to project.
+     * @param case_sensitive_match Whether to use case-sensitive match for string comparison.
+     * @return A result containing the newly constructed `QueryHandler` on success, or an error code
+     * indicating the failure:
+     * - Forwards `preprocess_query`'s return values.
+     * - Forwards `create_projected_columns_and_projection_map`'s return values.
+     * - Forwards `create_initial_partial_resolutions`'s return values.
+     */
+    [[nodiscard]] static auto create(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            std::vector<std::pair<std::string, clp_s::search::ast::LiteralTypeBitmask>> const&
+                    projections,
+            bool case_sensitive_match
+    ) -> outcome_v2::std_result<QueryHandlerImpl>;
+
+    // Delete copy constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl const&) = delete;
+    auto operator=(QueryHandlerImpl const&) -> QueryHandlerImpl& = delete;
+
+    // Default move constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl&&) = default;
+    auto operator=(QueryHandlerImpl&&) -> QueryHandlerImpl& = default;
+
+    // Destructor
+    ~QueryHandlerImpl() = default;
+
+    /**
+     * Implementation of `QueryHandler::evaluate_node_id_value_pairs`.
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - TODO
+     */
+    [[nodiscard]] auto evaluate_node_id_value_pairs(
+            KeyValuePairLogEvent::NodeIdValuePairs const& auto_gen_node_id_value_pairs,
+            KeyValuePairLogEvent::NodeIdValuePairs const& user_gen_node_id_value_pairs
+    ) -> outcome_v2::std_result<AstEvaluationResult>;
+
+    /**
+     * Implementation of `QueryHandler::update_partially_resolved_columns` with new projected
+     * schema-tree node callback given as a template parameter.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @param new_projected_schema_tree_node_callback
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `handle_column_resolution_on_leaf_node`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto update_partially_resolved_columns(
+            bool is_auto_generated,
+            SchemaTree::NodeLocator const& node_locator,
+            SchemaTree::Node::id_t node_id,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    [[nodiscard]] auto get_resolved_column_to_schema_tree_node_ids() const -> std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>> {
+        return m_resolved_column_to_schema_tree_node_ids;
+    }
+
+private:
+    // Constructor
+    QueryHandlerImpl(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            PartialResolutionMap auto_gen_namespace_partial_resolutions,
+            PartialResolutionMap user_gen_namespace_partial_resolutions,
+            std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> projected_columns,
+            ProjectionMap projected_column_to_original_key,
+            bool case_sensitive_match
+    )
+            : m_query{std::move(query)},
+              m_auto_gen_namespace_partial_resolutions{
+                      std::move(auto_gen_namespace_partial_resolutions)
+              },
+              m_user_gen_namespace_partial_resolutions{
+                      std::move(user_gen_namespace_partial_resolutions)
+              },
+              m_projected_columns{std::move(projected_columns)},
+              m_projected_column_to_original_key{std::move(projected_column_to_original_key)},
+              m_case_sensitive_search{case_sensitive_match} {}
+
+    // Methods
+    /**
+     * Handles column resolution on the given schema-tree leaf node and the token in the column.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param is_auto_generated
+     * @param leaf_node_id
+     * @param leaf_node_locator
+     * @param token_it The current resolved token in the column.
+     * @param new_projected_schema_tree_node_callback
+     * @return A void result on success, or an error code indicating the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `new_projected_schema_tree_node_callback`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto handle_column_resolution_on_leaf_node(
+            bool is_auto_generated,
+            SchemaTree::Node::id_t leaf_node_id,
+            SchemaTree::NodeLocator const& leaf_node_locator,
+            ColumnDescriptorTokenIterator const& token_it,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    // Variables
+    std::shared_ptr<clp_s::search::ast::Expression> m_query;
+    PartialResolutionMap m_auto_gen_namespace_partial_resolutions;
+    PartialResolutionMap m_user_gen_namespace_partial_resolutions;
+    std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>>
+            m_resolved_column_to_schema_tree_node_ids;
+    std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> m_projected_columns;
+    ProjectionMap m_projected_column_to_original_key;
+    bool m_case_sensitive_search;
+};
+
+template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+auto QueryHandlerImpl::update_partially_resolved_columns(
+        bool is_auto_generated,
+        SchemaTree::NodeLocator const& node_locator,
+        SchemaTree::Node::id_t node_id,
+        NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+) -> outcome_v2::std_result<void> {
+    auto const parent_node_id{node_locator.get_parent_id()};
+    auto& partial_resolutions_to_update{
+            is_auto_generated ? m_auto_gen_namespace_partial_resolutions
+                              : m_user_gen_namespace_partial_resolutions
+    };
+    if (false == partial_resolutions_to_update.contains(parent_node_id)) {
+        return outcome_v2::success();
+    }
+
+    for (auto const& token_it : partial_resolutions_to_update.at(parent_node_id)) {
+        auto const next_token_it_result{token_it.next()};
+        if (SchemaTree::Node::Type::Obj == node_locator.get_type()) {
+            // Handle schema-tree non-leaf nodes.
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: coderabbitai[bot]
body: @LinZhihao-723 Thanks for fixing those issues! The implementation looks good - it safely collects new partial resolutions before adding them to the map and properly handles the token iterator results.

---

<!-- This is an auto-generated comment: tweet message by coderabbit.ai -->

If you found this review helpful, would you consider giving us a shout-out on [X](https://twitter.com/intent/tweet?related=CodeRabbitAI&text=Avoid%20iterator%20invalidation%20and%20hidden%20errors%20by%20copying%20vectors%20before%20mutation%20during%20iteration!%20Safer%2C%20cleaner%20code%20with%20%40coderabbitai%20%F0%9F%9B%A0%EF%B8%8F%E2%9C%A8%20Try%20it%20to%20catch%20subtle%20bugs%20early!&url=https%3A%2F%2Fgithub.com%2Fy-scope%2Fclp%2Fpull%2F873%23discussion_r2072136032)?

Thank you for using CodeRabbit!

<!-- end of auto-generated comment: tweet message by coderabbit.ai -->




<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@LinZhihao-723 Thanks for fixing those issues! The implementation looks good - it safely collects new partial resolutions before adding them to the map and properly handles the token iterator results.

---

&lt;!-- This is an auto-generated comment: tweet message by coderabbit.ai --&gt;

If you found this review helpful, would you consider giving us a shout-out on [X](https://twitter.com/intent/tweet?related=CodeRabbitAI&text=Avoid%20iterator%20invalidation%20and%20hidden%20errors%20by%20copying%20vectors%20before%20mutation%20during%20iteration!%20Safer%2C%20cleaner%20code%20with%20%40coderabbitai%20%F0%9F%9B%A0%EF%B8%8F%E2%9C%A8%20Try%20it%20to%20catch%20subtle%20bugs%20early!&url=https%3A%2F%2Fgithub.com%2Fy-scope%2Fclp%2Fpull%2F873%23discussion_r2072136032)?

Thank you for using CodeRabbit!

&lt;!-- end of auto-generated comment: tweet message by coderabbit.ai --&gt;

</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T22:40:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,344 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param colum_descriptor
+         * @return A result containing the constructed token iterator on success, or an error code
+         * indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the column doesn't have any
+         *   token.
+         */
+        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* colum_descriptor)
+                -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            auto const token_begin_it{colum_descriptor->descriptor_begin()};
+            if (colum_descriptor->descriptor_end() == token_begin_it) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound};
+            }
+            return ColumnDescriptorTokenIterator{colum_descriptor, token_begin_it};
+        }
+
+        // Methods
+        /**
+         * @return Whether this is the last token of the underlying column descriptor.
+         */
+        [[nodiscard]] auto is_last() const -> bool {
+            return m_column_descriptor->descriptor_end() == m_next_token_it;
+        }
+
+        /**
+         * @return A result containing a newly constructor iterator pointing to the next token on
+         * success, or an error code indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the current token is already
+         *   the last one.
+         */
+        [[nodiscard]] auto next() const -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            if (is_last()) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound};
+            }
+            return ColumnDescriptorTokenIterator{m_column_descriptor, m_next_token_it};
+        }
+
+        [[nodiscard]] auto is_wildcard() const -> bool { return m_curr_token_it->wildcard(); }
+
+        [[nodiscard]] auto get_token() const -> std::string_view {
+            return m_curr_token_it->get_token();
+        }
+
+        [[nodiscard]] auto get_column_descriptor() const -> clp_s::search::ast::ColumnDescriptor* {
+            return m_column_descriptor;
+        }
+
+        /**
+         * @param type
+         * @return Whether the underlying column can match the given schema-tree node type.
+         */
+        [[nodiscard]] auto match(SchemaTree::Node::Type type) const -> bool {
+            return m_column_descriptor->matches_any(schema_tree_node_type_to_literal_types(type));
+        }
+
+    private:
+        // Constructor
+        ColumnDescriptorTokenIterator(
+                clp_s::search::ast::ColumnDescriptor* colum_descriptor,
+                clp_s::search::ast::DescriptorList::iterator curr_token_it
+        )
+                : m_column_descriptor{colum_descriptor},
+                  m_curr_token_it{curr_token_it},
+                  m_next_token_it{curr_token_it + 1} {}
+
+        clp_s::search::ast::ColumnDescriptor* m_column_descriptor;
+        clp_s::search::ast::DescriptorList::iterator m_curr_token_it;
+        clp_s::search::ast::DescriptorList::iterator m_next_token_it;
+    };
+
+    using ProjectionMap = std::unordered_map<clp_s::search::ast::ColumnDescriptor*, std::string>;
+
+    using PartialResolutionMap = std::
+            unordered_map<SchemaTree::Node::id_t, std::vector<ColumnDescriptorTokenIterator>>;
+
+    // Factory function
+    /**
+     * @param query The search query.
+     * @param projections The columns to project.
+     * @param case_sensitive_match Whether to use case-sensitive match for string comparison.
+     * @return A result containing the newly constructed `QueryHandler` on success, or an error code
+     * indicating the failure:
+     * - Forwards `preprocess_query`'s return values.
+     * - Forwards `create_projected_columns_and_projection_map`'s return values.
+     * - Forwards `create_initial_partial_resolutions`'s return values.
+     */
+    [[nodiscard]] static auto create(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            std::vector<std::pair<std::string, clp_s::search::ast::LiteralTypeBitmask>> const&
+                    projections,
+            bool case_sensitive_match
+    ) -> outcome_v2::std_result<QueryHandlerImpl>;
+
+    // Delete copy constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl const&) = delete;
+    auto operator=(QueryHandlerImpl const&) -> QueryHandlerImpl& = delete;
+
+    // Default move constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl&&) = default;
+    auto operator=(QueryHandlerImpl&&) -> QueryHandlerImpl& = default;
+
+    // Destructor
+    ~QueryHandlerImpl() = default;
+
+    /**
+     * Implementation of `QueryHandler::evaluate_node_id_value_pairs`.
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - TODO
+     */
+    [[nodiscard]] auto evaluate_node_id_value_pairs(
+            KeyValuePairLogEvent::NodeIdValuePairs const& auto_gen_node_id_value_pairs,
+            KeyValuePairLogEvent::NodeIdValuePairs const& user_gen_node_id_value_pairs
+    ) -> outcome_v2::std_result<AstEvaluationResult>;
+
+    /**
+     * Implementation of `QueryHandler::update_partially_resolved_columns` with new projected
+     * schema-tree node callback given as a template parameter.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @param new_projected_schema_tree_node_callback
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `handle_column_resolution_on_leaf_node`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto update_partially_resolved_columns(
+            bool is_auto_generated,
+            SchemaTree::NodeLocator const& node_locator,
+            SchemaTree::Node::id_t node_id,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    [[nodiscard]] auto get_resolved_column_to_schema_tree_node_ids() const -> std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::vector<SchemaTree::Node::id_t>> {
+        return m_resolved_column_to_schema_tree_node_ids;
+    }
+
+private:
+    // Constructor
+    QueryHandlerImpl(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            PartialResolutionMap auto_gen_namespace_partial_resolutions,
+            PartialResolutionMap user_gen_namespace_partial_resolutions,
+            std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> projected_columns,
+            ProjectionMap projected_column_to_original_key,
+            bool case_sensitive_match
+    )
+            : m_query{std::move(query)},
+              m_auto_gen_namespace_partial_resolutions{
+                      std::move(auto_gen_namespace_partial_resolutions)
+              },
+              m_user_gen_namespace_partial_resolutions{
+                      std::move(user_gen_namespace_partial_resolutions)
+              },
+              m_projected_columns{std::move(projected_columns)},
+              m_projected_column_to_original_key{std::move(projected_column_to_original_key)},
+              m_case_sensitive_search{case_sensitive_match} {}
+
+    // Methods
+    /**
+     * Handles column resolution on the given schema-tree leaf node and the token in the column.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param is_auto_generated
+     * @param leaf_node_id
+     * @param leaf_node_locator
+     * @param token_it The current resolved token in the column.
+     * @param new_projected_schema_tree_node_callback
+     * @return A void result on success, or an error code indicating the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `new_projected_schema_tree_node_callback`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto handle_column_resolution_on_leaf_node(
+            bool is_auto_generated,
+            SchemaTree::Node::id_t leaf_node_id,
+            SchemaTree::NodeLocator const& leaf_node_locator,
+            ColumnDescriptorTokenIterator const& token_it,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    // Variables
+    std::shared_ptr<clp_s::search::ast::Expression> m_query;
+    PartialResolutionMap m_auto_gen_namespace_partial_resolutions;
+    PartialResolutionMap m_user_gen_namespace_partial_resolutions;
+    std::unordered_map<clp_s::search::ast::ColumnDescriptor*, std::vector<SchemaTree::Node::id_t>>
+            m_resolved_column_to_schema_tree_node_ids;
+    std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> m_projected_columns;
+    ProjectionMap m_projected_column_to_original_key;
+    bool m_case_sensitive_search;
+};
+
+template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+auto QueryHandlerImpl::update_partially_resolved_columns(
+        bool is_auto_generated,
+        SchemaTree::NodeLocator const& node_locator,
+        SchemaTree::Node::id_t node_id,
+        NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+) -> outcome_v2::std_result<void> {
+    auto const parent_node_id{node_locator.get_parent_id()};
+    auto& partial_resolutions_to_update{
+            is_auto_generated ? m_auto_gen_namespace_partial_resolutions
+                              : m_user_gen_namespace_partial_resolutions
+    };
+    if (false == partial_resolutions_to_update.contains(parent_node_id)) {
+        return outcome_v2::success();
+    }
+
+    for (auto const& token_it : partial_resolutions_to_update.at(parent_node_id)) {
+        auto const next_token_it_result{token_it.next()};
+        if (SchemaTree::Node::Type::Obj == node_locator.get_type()) {
+            // Handle schema-tree non-leaf nodes.
+            if (token_it.is_last()) {
+                // TODO: Handle object search when supported.
+                continue;
+            }
+
+            if (token_it.is_wildcard()) {
+                auto [it, inserted] = partial_resolutions_to_update.try_emplace(
+                        node_id,
+                        std::vector<ColumnDescriptorTokenIterator>{}
+                );
+                it->second.emplace_back(token_it);
+                it->second.emplace_back(OUTCOME_TRYX(token_it.next()));
+                continue;
+            }
+
+            if (token_it.get_token() != node_locator.get_key_name()) {
+                continue;
+            }
+
+            auto const next_token_it{OUTCOME_TRYX(token_it.next())};
+            auto [it, inserted] = partial_resolutions_to_update.try_emplace(
+                    node_id,
+                    std::vector<ColumnDescriptorTokenIterator>{}
+            );
+            it->second.emplace_back(next_token_it);
+            if (false == next_token_it.is_last() && next_token_it.is_wildcard()) {
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: coderabbitai[bot]
body: @LinZhihao-723 I've verified the fix, and it looks great! The approach of collecting partial resolutions in a temporary vector before modifying the map completely addresses the iterator invalidation issue. Nice work! 👍

---

<!-- This is an auto-generated comment: tweet message by coderabbit.ai -->

If you found this review helpful, would you consider giving us a shout-out on [X](https://twitter.com/intent/tweet?related=CodeRabbitAI&text=Avoid%20iterator%20invalidation%20when%20modifying%20an%20unordered_map%20during%20iteration!%20Copy%20the%20vector%20first%2C%20then%20insert.%20Prevent%20subtle%20bugs%20with%20%40coderabbitai%E2%80%99s%20smart%20review%20tips%20%F0%9F%90%87%F0%9F%94%A7&url=https%3A%2F%2Fgithub.com%2Fy-scope%2Fclp%2Fpull%2F873%23discussion_r2070610347)?

Thank you for using CodeRabbit!

<!-- end of auto-generated comment: tweet message by coderabbit.ai -->




<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@LinZhihao-723 I've verified the fix, and it looks great! The approach of collecting partial resolutions in a temporary vector before modifying the map completely addresses the iterator invalidation issue. Nice work! 👍

---

&lt;!-- This is an auto-generated comment: tweet message by coderabbit.ai --&gt;

If you found this review helpful, would you consider giving us a shout-out on [X](https://twitter.com/intent/tweet?related=CodeRabbitAI&text=Avoid%20iterator%20invalidation%20when%20modifying%20an%20unordered_map%20during%20iteration!%20Copy%20the%20vector%20first%2C%20then%20insert.%20Prevent%20subtle%20bugs%20with%20%40coderabbitai%E2%80%99s%20smart%20review%20tips%20%F0%9F%90%87%F0%9F%94%A7&url=https%3A%2F%2Fgithub.com%2Fy-scope%2Fclp%2Fpull%2F873%23discussion_r2070610347)?

Thank you for using CodeRabbit!

&lt;!-- end of auto-generated comment: tweet message by coderabbit.ai --&gt;

</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T22:40:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,324 @@
+#include "QueryHandlerImpl.hpp"
+
+#include <exception>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/archive_constants.hpp"
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/ConvertToExists.hpp"
+#include "../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../clp_s/search/ast/NarrowTypes.hpp"
+#include "../../../../clp_s/search/ast/OrOfAndForm.hpp"
+#include "../../../../clp_s/search/ast/SearchUtils.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::EmptyExpr;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Pre-processes a search query by applying several transformation passes.
+ * @param query
+ * @return A result containing the transformed
+ * - ErrorCodeEnum::QueryExpressionIsNull if `query` is nullptr.
+ * - ErrorCodeEnum::QueryTransformationPassFailed if any of the transformation pass failed.
+ */
+[[nodiscard]] auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>>;
+
+/**
+ * Creates projected columns and column-to-original-key map from the given projections.
+ * @param projections
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - A vector of projected columns.
+ *   - A projected-column-to-original-key map.
+ * - The possible error codes:
+ *   - ErrorCodeEnum::DuplicateProjectedColumn if `projections` contains duplicated keys.
+ *   - ErrorCodeEnum::ColumnTokenizationFailure if failed to tokenize a projection key.
+ *   - ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure if failed to create a column
+ *     descriptor for the projected key.
+ */
+[[nodiscard]] auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>>;
+
+/**
+ * Creates initial partial resolutions for the given query and projections.
+ * @param query
+ * @param projected_column_to_original_key
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - The partial resolution for auto-generated keys.
+ *   - The partial resolution for user-generated keys.
+ * - The possible error codes:
+ *   - Forwards `is_auto_generated`'s return values.
+ *   - Forwards `initialize_partial_resolution_from_search_ast`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::create`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::next`'s return values.
+ */
+[[nodiscard]] auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>>;
+
+/**
+ * Initializes partial resolutions from a search AST.
+ * @param root The root of the search AST.
+ * @param auto_gen_namespace_partial_resolutions Returns initialized auto-gen partial resolutions.
+ * @param user_gen_namespace_partial_resolutions Returns initialized user-gen partial resolutions.
+ * @return A void result on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::AstDynamicCastFailure if failed to dynamically cast an AST node to a target
+ *   type.
+ * - Forwards `is_auto_generated`'s return values.
+ */
+[[nodiscard]] auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void>;
+
+/**
+ * @param key_namespace
+ * @return A result containing a boolean indicating whether `key_namespace` is auto-generated or
+ * user-generated on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::UnsupportedNamespace if the namespace is not supported.
+ */
+[[nodiscard]] auto is_auto_generated(std::string_view key_namespace)
+        -> outcome_v2::std_result<bool>;
+
+auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>> {
+    if (nullptr == query) {
+        return ErrorCode{ErrorCodeEnum::QueryExpressionIsNull};
+    }
+
+    if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(query)) {
+        return query;
+    }
+
+    try {
+        if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        if (query = clp_s::search::ast::NarrowTypes{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        return clp_s::search::ast::ConvertToExists{}.run(query);
+    } catch (std::exception const& ex) {
+        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+    }
+}
+
+auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>> {
+    std::unordered_set<std::string_view> unique_projected_columns;
+    std::vector<std::shared_ptr<ColumnDescriptor>> projected_columns;
+    QueryHandlerImpl::ProjectionMap projected_column_to_original_key;
+
+    for (auto const& [key, types] : projections) {
+        if (unique_projected_columns.contains(key)) {
+            return ErrorCode{ErrorCodeEnum::DuplicateProjectedColumn};
+        }
+        unique_projected_columns.emplace(key);
+
+        std::vector<std::string> descriptor_tokens;
+        std::string descriptor_namespace;
+        if (false
+            == clp_s::search::ast::tokenize_column_descriptor(
+                    key,
+                    descriptor_tokens,
+                    descriptor_namespace
+            ))
+        {
+            return ErrorCode{ErrorCodeEnum::ColumnTokenizationFailure};
+        }
+
+        try {
+            auto column_descriptor{clp_s::search::ast::ColumnDescriptor::create_from_escaped_tokens(
+                    descriptor_tokens,
+                    descriptor_namespace
+            )};
+            column_descriptor->set_matching_types(types);
+            if (column_descriptor->is_unresolved_descriptor()
+                || column_descriptor->get_descriptor_list().empty())
+            {
+                return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+            }
+            projected_column_to_original_key.emplace(column_descriptor.get(), key);
+            projected_columns.emplace_back(std::move(column_descriptor));
+        } catch (std::exception const& e) {
+            return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+        }
+    }
+
+    return {std::move(projected_columns), std::move(projected_column_to_original_key)};
+}
+
+auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>> {
+    QueryHandlerImpl::PartialResolutionMap auto_gen_namespace_partial_resolutions;
+    QueryHandlerImpl::PartialResolutionMap user_gen_namespace_partial_resolutions;
+    for (auto const& [col, key] : projected_column_to_original_key) {
+        bool const is_auto_gen{OUTCOME_TRYX(is_auto_generated(col->get_namespace()))};
+        auto& partial_resolutions{
+                is_auto_gen ? auto_gen_namespace_partial_resolutions
+                            : user_gen_namespace_partial_resolutions
+        };
+        auto [it, inserted] = partial_resolutions.try_emplace(
+                SchemaTree::cRootId,
+                std::vector<QueryHandlerImpl::ColumnDescriptorTokenIterator>{}
+        );
+        it->second.emplace_back(
+                OUTCOME_TRYX(QueryHandlerImpl::ColumnDescriptorTokenIterator::create(col))
+        );
+    }
+
+    OUTCOME_TRYV(initialize_partial_resolution_from_search_ast(
+            query,
+            auto_gen_namespace_partial_resolutions,
+            user_gen_namespace_partial_resolutions
+    ));
+
+    return {std::move(auto_gen_namespace_partial_resolutions),
+            std::move(user_gen_namespace_partial_resolutions)};
+}
+
+auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void> {
+    std::vector<Expression*> ast_dfs_stack;
+    ast_dfs_stack.emplace_back(root.get());
+    while (false == ast_dfs_stack.empty()) {
+        auto* expr{ast_dfs_stack.back()};
+        ast_dfs_stack.pop_back();
+        if (expr->has_only_expression_operands()) {
+            for (auto it{expr->op_begin()}; it != expr->op_end(); ++it) {
+                auto* expr{dynamic_cast<Expression*>(it->get())};
+                if (nullptr == expr) {
+                    return ErrorCode{ErrorCodeEnum::AstDynamicCastFailure};
+                }
+                ast_dfs_stack.emplace_back(expr);
+            }
+            continue;
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
user: login: LinZhihao-723
body: Fixed.
created_at: 2025-05-02T22:41:53Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,324 @@
+#include "QueryHandlerImpl.hpp"
+
+#include <exception>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/archive_constants.hpp"
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/ConvertToExists.hpp"
+#include "../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../clp_s/search/ast/NarrowTypes.hpp"
+#include "../../../../clp_s/search/ast/OrOfAndForm.hpp"
+#include "../../../../clp_s/search/ast/SearchUtils.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::EmptyExpr;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Pre-processes a search query by applying several transformation passes.
+ * @param query
+ * @return A result containing the transformed
+ * - ErrorCodeEnum::QueryExpressionIsNull if `query` is nullptr.
+ * - ErrorCodeEnum::QueryTransformationPassFailed if any of the transformation pass failed.
+ */
+[[nodiscard]] auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>>;
+
+/**
+ * Creates projected columns and column-to-original-key map from the given projections.
+ * @param projections
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - A vector of projected columns.
+ *   - A projected-column-to-original-key map.
+ * - The possible error codes:
+ *   - ErrorCodeEnum::DuplicateProjectedColumn if `projections` contains duplicated keys.
+ *   - ErrorCodeEnum::ColumnTokenizationFailure if failed to tokenize a projection key.
+ *   - ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure if failed to create a column
+ *     descriptor for the projected key.
+ */
+[[nodiscard]] auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>>;
+
+/**
+ * Creates initial partial resolutions for the given query and projections.
+ * @param query
+ * @param projected_column_to_original_key
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - The partial resolution for auto-generated keys.
+ *   - The partial resolution for user-generated keys.
+ * - The possible error codes:
+ *   - Forwards `is_auto_generated`'s return values.
+ *   - Forwards `initialize_partial_resolution_from_search_ast`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::create`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::next`'s return values.
+ */
+[[nodiscard]] auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>>;
+
+/**
+ * Initializes partial resolutions from a search AST.
+ * @param root The root of the search AST.
+ * @param auto_gen_namespace_partial_resolutions Returns initialized auto-gen partial resolutions.
+ * @param user_gen_namespace_partial_resolutions Returns initialized user-gen partial resolutions.
+ * @return A void result on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::AstDynamicCastFailure if failed to dynamically cast an AST node to a target
+ *   type.
+ * - Forwards `is_auto_generated`'s return values.
+ */
+[[nodiscard]] auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void>;
+
+/**
+ * @param key_namespace
+ * @return A result containing a boolean indicating whether `key_namespace` is auto-generated or
+ * user-generated on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::UnsupportedNamespace if the namespace is not supported.
+ */
+[[nodiscard]] auto is_auto_generated(std::string_view key_namespace)
+        -> outcome_v2::std_result<bool>;
+
+auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>> {
+    if (nullptr == query) {
+        return ErrorCode{ErrorCodeEnum::QueryExpressionIsNull};
+    }
+
+    if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(query)) {
+        return query;
+    }
+
+    try {
+        if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        if (query = clp_s::search::ast::NarrowTypes{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        return clp_s::search::ast::ConvertToExists{}.run(query);
+    } catch (std::exception const& ex) {
+        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+    }
+}
+
+auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>> {
+    std::unordered_set<std::string_view> unique_projected_columns;
+    std::vector<std::shared_ptr<ColumnDescriptor>> projected_columns;
+    QueryHandlerImpl::ProjectionMap projected_column_to_original_key;
+
+    for (auto const& [key, types] : projections) {
+        if (unique_projected_columns.contains(key)) {
+            return ErrorCode{ErrorCodeEnum::DuplicateProjectedColumn};
+        }
+        unique_projected_columns.emplace(key);
+
+        std::vector<std::string> descriptor_tokens;
+        std::string descriptor_namespace;
+        if (false
+            == clp_s::search::ast::tokenize_column_descriptor(
+                    key,
+                    descriptor_tokens,
+                    descriptor_namespace
+            ))
+        {
+            return ErrorCode{ErrorCodeEnum::ColumnTokenizationFailure};
+        }
+
+        try {
+            auto column_descriptor{clp_s::search::ast::ColumnDescriptor::create_from_escaped_tokens(
+                    descriptor_tokens,
+                    descriptor_namespace
+            )};
+            column_descriptor->set_matching_types(types);
+            if (column_descriptor->is_unresolved_descriptor()
+                || column_descriptor->get_descriptor_list().empty())
+            {
+                return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+            }
+            projected_column_to_original_key.emplace(column_descriptor.get(), key);
+            projected_columns.emplace_back(std::move(column_descriptor));
+        } catch (std::exception const& e) {
+            return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+        }
+    }
+
+    return {std::move(projected_columns), std::move(projected_column_to_original_key)};
+}
+
+auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>> {
+    QueryHandlerImpl::PartialResolutionMap auto_gen_namespace_partial_resolutions;
+    QueryHandlerImpl::PartialResolutionMap user_gen_namespace_partial_resolutions;
+    for (auto const& [col, key] : projected_column_to_original_key) {
+        bool const is_auto_gen{OUTCOME_TRYX(is_auto_generated(col->get_namespace()))};
+        auto& partial_resolutions{
+                is_auto_gen ? auto_gen_namespace_partial_resolutions
+                            : user_gen_namespace_partial_resolutions
+        };
+        auto [it, inserted] = partial_resolutions.try_emplace(
+                SchemaTree::cRootId,
+                std::vector<QueryHandlerImpl::ColumnDescriptorTokenIterator>{}
+        );
+        it->second.emplace_back(
+                OUTCOME_TRYX(QueryHandlerImpl::ColumnDescriptorTokenIterator::create(col))
+        );
+    }
+
+    OUTCOME_TRYV(initialize_partial_resolution_from_search_ast(
+            query,
+            auto_gen_namespace_partial_resolutions,
+            user_gen_namespace_partial_resolutions
+    ));
+
+    return {std::move(auto_gen_namespace_partial_resolutions),
+            std::move(user_gen_namespace_partial_resolutions)};
+}
+
+auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void> {
+    std::vector<Expression*> ast_dfs_stack;
+    ast_dfs_stack.emplace_back(root.get());
+    while (false == ast_dfs_stack.empty()) {
+        auto* expr{ast_dfs_stack.back()};
+        ast_dfs_stack.pop_back();
+        if (expr->has_only_expression_operands()) {
+            for (auto it{expr->op_begin()}; it != expr->op_end(); ++it) {
+                auto* expr{dynamic_cast<Expression*>(it->get())};
+                if (nullptr == expr) {
+                    return ErrorCode{ErrorCodeEnum::AstDynamicCastFailure};
+                }
+                ast_dfs_stack.emplace_back(expr);
+            }
+            continue;
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: @LinZhihao-723 Great! I've verified that the variable shadowing issue has been fixed. Thank you for making this change, it will help with code clarity and prevent potential bugs.

---

<!-- This is an auto-generated comment: tweet message by coderabbit.ai -->

If you found this review helpful, would you consider giving us a shout-out on [X](https://twitter.com/intent/tweet?related=CodeRabbitAI&text=Avoid%20variable%20shadowing%20in%20loops%20to%20boost%20code%20clarity%20and%20prevent%20subtle%20bugs!%20Rename%20inner%20vars%20for%20better%20readability%20and%20safer%20maintenance.%20Try%20%40coderabbitai%20for%20smart%20reviews!%20%F0%9F%90%87%E2%9C%A8&url=https%3A%2F%2Fgithub.com%2Fy-scope%2Fclp%2Fpull%2F873%23discussion_r2072136039)?

Thank you for using CodeRabbit!

<!-- end of auto-generated comment: tweet message by coderabbit.ai -->




<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@LinZhihao-723 Great! I've verified that the variable shadowing issue has been fixed. Thank you for making this change, it will help with code clarity and prevent potential bugs.

---

&lt;!-- This is an auto-generated comment: tweet message by coderabbit.ai --&gt;

If you found this review helpful, would you consider giving us a shout-out on [X](https://twitter.com/intent/tweet?related=CodeRabbitAI&text=Avoid%20variable%20shadowing%20in%20loops%20to%20boost%20code%20clarity%20and%20prevent%20subtle%20bugs!%20Rename%20inner%20vars%20for%20better%20readability%20and%20safer%20maintenance.%20Try%20%40coderabbitai%20for%20smart%20reviews!%20%F0%9F%90%87%E2%9C%A8&url=https%3A%2F%2Fgithub.com%2Fy-scope%2Fclp%2Fpull%2F873%23discussion_r2072136039)?

Thank you for using CodeRabbit!

&lt;!-- end of auto-generated comment: tweet message by coderabbit.ai --&gt;

</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T22:42:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,164 @@
+#include "utils.hpp"
+
+#include <bitset>
+#include <cstddef>
+#include <map>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <fmt/core.h>
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../SchemaTree.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::search::ast::LiteralType;
+}  // namespace
+
+auto ColumnQueryPossibleMatches::get_matchable_node_ids_from_literal_type(LiteralType type) const
+        -> std::vector<SchemaTree::Node::id_t> {
+    if ((type & m_matchable_types) == 0) {
+        return {};
+    }
+
+    switch (type) {
+        case LiteralType::IntegerT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Int);
+        case LiteralType::FloatT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Float);
+        case LiteralType::BooleanT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Bool);
+        case LiteralType::VarStringT:
+        case LiteralType::ClpStringT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Str);
+        default:
+            return {};
+    }
+}
+
+auto ColumnQueryPossibleMatches::get_matchable_node_ids_from_schema_tree_type(
+        SchemaTree::Node::Type type
+) const -> std::vector<SchemaTree::Node::id_t> {
+    std::vector<SchemaTree::Node::id_t> node_ids;
+    for (auto const node_id : m_matchable_node_ids) {
+        if (m_schema_tree->get_node(node_id).get_type() == type) {
+            node_ids.emplace_back(node_id);
+        }
+    }
+    return node_ids;
+}
+
+auto ColumnQueryPossibleMatches::get_projectable_node_ids() const
+        -> std::vector<SchemaTree::Node::id_t> {
+    std::vector<SchemaTree::Node::id_t> projectable_node_ids;
+    for (auto const node_id : m_matchable_node_ids) {
+        if (m_schema_tree->get_node(node_id).get_type() != SchemaTree::Node::Type::Obj) {
+            projectable_node_ids.emplace_back(node_id);
+        }
+    }
+    return projectable_node_ids;
+}
+
+auto ColumnQueryPossibleMatches::serialize() const -> std::string {
+    constexpr size_t cBitsetSize{sizeof(m_matchable_types) * 8};
+    return fmt::format(
+            "Types: {}; Node IDs: {{{}}}",
+            std::bitset<cBitsetSize>(m_matchable_types).to_string(),
+            fmt::join(m_matchable_node_ids, ",")
+    );
+}
+
+auto trivial_new_projected_schema_tree_node_callback(
+        [[maybe_unused]] bool is_auto_generated,
+        [[maybe_unused]] SchemaTree::Node::id_t node_id,
+        [[maybe_unused]] std::string_view projected_key_path
+) -> outcome_v2::std_result<void> {
+    return outcome_v2::success();
+}
path: components/core/src/clp/ffi/ir_stream/search/test/utils.cpp
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**Unused function that may be needed for tests.**

The static analyzer flags this function as unused, but it appears to be used in test files. If this is truly unused, consider removing it or adding a comment explaining its purpose for future use.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if trivial_new_projected_schema_tree_node_callback is used in test files
rg -A 2 "trivial_new_projected_schema_tree_node_callback" --type cpp
```

Length of output: 1519

---



</details>

**Document test callback to avoid static-analyzer false positive**  
The `trivial_new_projected_schema_tree_node_callback` defined in  
`components/core/src/clp/ffi/ir_stream/search/test/utils.cpp` (and declared in `utils.hpp`)  
is referenced in `test_QueryHandlerImpl.cpp`. It’s not safe to remove—static analysis is emitting  
a false-positive for an unused function.  

To prevent future confusion and suppress the warning, please add a brief comment at the definition (or declaration) indicating that this callback is used by tests. For example:

```cpp
// Callback used by test_QueryHandlerImpl to simulate a projected schema-tree node.
auto trivial_new_projected_schema_tree_node_callback(
    [[maybe_unused]] bool is_auto_generated,
    [[maybe_unused]] SchemaTree::Node::id_t node_id,
    [[maybe_unused]] std::string_view projected_key_path
) -> outcome_v2::std_result<void> {
    return outcome_v2::success();
}
```

• File locations to annotate:  
  - components/core/src/clp/ffi/ir_stream/search/test/utils.hpp  
  - components/core/src/clp/ffi/ir_stream/search/test/utils.cpp  
• Usage sites confirmed in:  
  - components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Cppcheck (2.10-2)</summary>

[style] 78-78: The function 'trivial_new_projected_schema_tree_node_callback' is never used.

(unusedFunction)

</details>

</details>

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T22:48:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,446 @@
+#include <map>
+#include <memory>
+#include <set>
+#include <sstream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <catch2/catch.hpp>
+#include <fmt/core.h>
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/archive_constants.hpp"
+#include "../../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../../clp_s/search/kql/kql.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../../../Value.hpp"
+#include "../QueryHandlerImpl.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::search::ast::LiteralTypeBitmask;
+
+constexpr std::string_view cRefTestStr{"*test*"};
+constexpr value_int_t cRefTestInt{0};
+constexpr value_float_t cRefTestFloat{0.0};
+constexpr value_bool_t cRefTestBool{false};
+
+/**
+ * Generates all the matchable KQL expressions based on the column queries and their matchable
+ * types. For each column query `C` matched type `T`, we generate an expression of `C: cRefTestT`,
+ * where `cRefTestT` is any of the constants defined above.
+ * @param is_auto_generated Whether the query is in auto-generated namespace.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - A vector of matchable KQL expressions.
+ * - A map of expected column resolutions.
+ */
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        bool is_auto_generated,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param is_auto_generated Whether the query is in auto-generated namespace.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - Generated projections as pairs, where each pair consists of:
+ *   - A projectable column from `column_query_to_possible_matches` that has no wildcard.
+ *   - All the matchable types of this column.
+ * - A map of expected resolved projections.
+ */
+[[nodiscard]] auto generate_projections(
+        bool is_auto_generated,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+)
+        -> std::pair<
+                std::vector<std::pair<std::string, LiteralTypeBitmask>>,
+                std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param column_resolutions
+ * @return The serialized pairs.
+ */
+[[nodiscard]] auto serialize_column_node_ids_map(
+        std::map<std::string, std::set<SchemaTree::Node::id_t>> const& column_resolutions
+) -> std::string;
+
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        bool is_auto_generated,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>> {
+    std::vector<std::string> matchable_kql_expressions;
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> expected_column_resolutions;
+    for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
+        auto const column_query_with_namespace{fmt::format(
+                "{}{}",
+                is_auto_generated ? clp_s::constants::cAutogenNamespace
+                                  : clp_s::constants::cDefaultNamespace,
+                column_query
+        )};
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Int
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestInt)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Float
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {:.2f}", column_query_with_namespace, cRefTestFloat)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Bool
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestBool)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Str
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestStr)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+    }
+
+    auto const single_wildcard{fmt::format(
+            "{}{}",
+            is_auto_generated ? clp_s::constants::cAutogenNamespace
+                              : clp_s::constants::cDefaultNamespace,
+            "*"
+    )};
+    if (expected_column_resolutions.contains(single_wildcard)) {
+        // NOTE: The current implementation doesn't resolve single wildcard.
+        expected_column_resolutions.erase(single_wildcard);
+    }
+    return {std::move(matchable_kql_expressions), std::move(expected_column_resolutions)};
+}
path: components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Complex helper could benefit from additional unit tests.**

The `generate_matchable_kql_expressions` function is complex and handles multiple types and edge cases. Consider adding dedicated unit tests for this helper to ensure it generates the expected expressions and resolutions for each type.

```cpp
// Example test for the helper function
TEST_CASE("generate_matchable_kql_expressions", "[ffi][ir_stream][search][utils]") {
    // Create a simple schema tree with known nodes of each type
    auto schema_tree = std::make_shared<SchemaTree>();
    // Add nodes of different types...
    
    auto column_query_to_possible_matches = get_schema_tree_column_queries(schema_tree);
    
    // Test auto-generated namespace
    auto [expressions, resolutions] = 
        generate_matchable_kql_expressions(true, column_query_to_possible_matches);
    
    // Verify expected expressions and resolutions for each type
    REQUIRE(expressions.size() > 0);
    // More specific checks...
}
```

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T22:48:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,328 @@
+#include "QueryHandlerImpl.hpp"
+
+#include <exception>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/archive_constants.hpp"
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/ConvertToExists.hpp"
+#include "../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../clp_s/search/ast/NarrowTypes.hpp"
+#include "../../../../clp_s/search/ast/OrOfAndForm.hpp"
+#include "../../../../clp_s/search/ast/SearchUtils.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::EmptyExpr;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Pre-processes a search query by applying several transformation passes.
+ * @param query
+ * @return A result containing the transformed
+ * - ErrorCodeEnum::QueryExpressionIsNull if `query` is nullptr.
+ * - ErrorCodeEnum::QueryTransformationPassFailed if any of the transformation pass failed.
+ */
+[[nodiscard]] auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>>;
+
+/**
+ * Creates projected columns and column-to-original-key map from the given projections.
+ * @param projections
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - A vector of projected columns.
+ *   - A projected-column-to-original-key map.
+ * - The possible error codes:
+ *   - ErrorCodeEnum::DuplicateProjectedColumn if `projections` contains duplicated keys.
+ *   - ErrorCodeEnum::ColumnTokenizationFailure if failed to tokenize a projection key.
+ *   - ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure if failed to create a column
+ *     descriptor for the projected key.
+ */
+[[nodiscard]] auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>>;
+
+/**
+ * Creates initial partial resolutions for the given query and projections.
+ * @param query
+ * @param projected_column_to_original_key
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - The partial resolution for auto-generated keys.
+ *   - The partial resolution for user-generated keys.
+ * - The possible error codes:
+ *   - Forwards `is_auto_generated`'s return values.
+ *   - Forwards `initialize_partial_resolution_from_search_ast`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::create`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::next`'s return values.
+ */
+[[nodiscard]] auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>>;
+
+/**
+ * Initializes partial resolutions from a search AST.
+ * @param root The root of the search AST.
+ * @param auto_gen_namespace_partial_resolutions Returns initialized auto-gen partial resolutions.
+ * @param user_gen_namespace_partial_resolutions Returns initialized user-gen partial resolutions.
+ * @return A void result on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::AstDynamicCastFailure if failed to dynamically cast an AST node to a target
+ *   type.
+ * - Forwards `is_auto_generated`'s return values.
+ */
+[[nodiscard]] auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void>;
+
+/**
+ * @param key_namespace
+ * @return A result containing a boolean indicating whether `key_namespace` is auto-generated or
+ * user-generated on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::UnsupportedNamespace if the namespace is not supported.
+ */
+[[nodiscard]] auto is_auto_generated(std::string_view key_namespace)
+        -> outcome_v2::std_result<bool>;
+
+auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>> {
+    if (nullptr == query) {
+        return ErrorCode{ErrorCodeEnum::QueryExpressionIsNull};
+    }
+
+    if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(query)) {
+        return query;
+    }
+
+    try {
+        if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        if (query = clp_s::search::ast::NarrowTypes{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        return clp_s::search::ast::ConvertToExists{}.run(query);
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Flatten chained assignment-in-`if` statements for clarity & easier debugging**

Embedding the transformation call *and* the null-check in a single `if` condition couples two concerns and makes single‐step debugging awkward.  
Creating an explicit temporary for the transformed AST improves readability and allows you to inspect intermediate results in a debugger.

```diff
-if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
-    nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
-{
-    return query;
-}
+auto transformed{clp_s::search::ast::OrOfAndForm{}.run(query)};
+if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(transformed)) {
+    return transformed;
+}
+query = std::move(transformed);
```

(The same pattern repeats for `NarrowTypes` below.)

Besides readability, this avoids the subtle risk of accidentally writing `==` instead of `=` in the assignment expression.  



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    try {
        auto transformed{clp_s::search::ast::OrOfAndForm{}.run(query)};
        if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(transformed)) {
            return transformed;
        }
        query = std::move(transformed);

        if (query = clp_s::search::ast::NarrowTypes{}.run(query);
            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
        {
            return query;
        }

        return clp_s::search::ast::ConvertToExists{}.run(query);
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T23:05:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,328 @@
+#include "QueryHandlerImpl.hpp"
+
+#include <exception>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/archive_constants.hpp"
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/ConvertToExists.hpp"
+#include "../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../clp_s/search/ast/NarrowTypes.hpp"
+#include "../../../../clp_s/search/ast/OrOfAndForm.hpp"
+#include "../../../../clp_s/search/ast/SearchUtils.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::EmptyExpr;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Pre-processes a search query by applying several transformation passes.
+ * @param query
+ * @return A result containing the transformed
+ * - ErrorCodeEnum::QueryExpressionIsNull if `query` is nullptr.
+ * - ErrorCodeEnum::QueryTransformationPassFailed if any of the transformation pass failed.
+ */
+[[nodiscard]] auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>>;
+
+/**
+ * Creates projected columns and column-to-original-key map from the given projections.
+ * @param projections
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - A vector of projected columns.
+ *   - A projected-column-to-original-key map.
+ * - The possible error codes:
+ *   - ErrorCodeEnum::DuplicateProjectedColumn if `projections` contains duplicated keys.
+ *   - ErrorCodeEnum::ColumnTokenizationFailure if failed to tokenize a projection key.
+ *   - ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure if failed to create a column
+ *     descriptor for the projected key.
+ */
+[[nodiscard]] auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>>;
+
+/**
+ * Creates initial partial resolutions for the given query and projections.
+ * @param query
+ * @param projected_column_to_original_key
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - The partial resolution for auto-generated keys.
+ *   - The partial resolution for user-generated keys.
+ * - The possible error codes:
+ *   - Forwards `is_auto_generated`'s return values.
+ *   - Forwards `initialize_partial_resolution_from_search_ast`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::create`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::next`'s return values.
+ */
+[[nodiscard]] auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>>;
+
+/**
+ * Initializes partial resolutions from a search AST.
+ * @param root The root of the search AST.
+ * @param auto_gen_namespace_partial_resolutions Returns initialized auto-gen partial resolutions.
+ * @param user_gen_namespace_partial_resolutions Returns initialized user-gen partial resolutions.
+ * @return A void result on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::AstDynamicCastFailure if failed to dynamically cast an AST node to a target
+ *   type.
+ * - Forwards `is_auto_generated`'s return values.
+ */
+[[nodiscard]] auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void>;
+
+/**
+ * @param key_namespace
+ * @return A result containing a boolean indicating whether `key_namespace` is auto-generated or
+ * user-generated on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::UnsupportedNamespace if the namespace is not supported.
+ */
+[[nodiscard]] auto is_auto_generated(std::string_view key_namespace)
+        -> outcome_v2::std_result<bool>;
+
+auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>> {
+    if (nullptr == query) {
+        return ErrorCode{ErrorCodeEnum::QueryExpressionIsNull};
+    }
+
+    if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(query)) {
+        return query;
+    }
+
+    try {
+        if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        if (query = clp_s::search::ast::NarrowTypes{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        return clp_s::search::ast::ConvertToExists{}.run(query);
+    } catch (std::exception const& ex) {
+        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+    }
+}
+
+auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>> {
+    std::unordered_set<std::string_view> unique_projected_columns;
+    std::vector<std::shared_ptr<ColumnDescriptor>> projected_columns;
+    QueryHandlerImpl::ProjectionMap projected_column_to_original_key;
+
+    for (auto const& [key, types] : projections) {
+        if (unique_projected_columns.contains(key)) {
+            return ErrorCode{ErrorCodeEnum::DuplicateProjectedColumn};
+        }
+        unique_projected_columns.emplace(key);
+
+        std::vector<std::string> descriptor_tokens;
+        std::string descriptor_namespace;
+        if (false
+            == clp_s::search::ast::tokenize_column_descriptor(
+                    key,
+                    descriptor_tokens,
+                    descriptor_namespace
+            ))
+        {
+            return ErrorCode{ErrorCodeEnum::ColumnTokenizationFailure};
+        }
+
+        try {
+            auto column_descriptor{clp_s::search::ast::ColumnDescriptor::create_from_escaped_tokens(
+                    descriptor_tokens,
+                    descriptor_namespace
+            )};
+            column_descriptor->set_matching_types(types);
+            if (column_descriptor->is_unresolved_descriptor()
+                || column_descriptor->get_descriptor_list().empty())
+            {
+                return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+            }
+            projected_column_to_original_key.emplace(column_descriptor.get(), key);
+            projected_columns.emplace_back(std::move(column_descriptor));
+        } catch (std::exception const& e) {
+            return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+        }
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Preserve exception context instead of swallowing `std::exception` message**

The `catch` block converts every failure to a generic `ProjectionColumnDescriptorCreationFailure`, discarding `e.what()`.  
If tokenisation succeeds yet descriptor construction throws (e.g., invalid escape sequence), the original diagnostic is lost, making triage harder.

Consider logging the message or wrapping it in a richer error:

```diff
-} catch (std::exception const& e) {
-    return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+} catch (std::exception const& e) {
+    // Preserve root cause for easier debugging
+    return ErrorCode{
+        ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure,
+        /*optional_msg*/ e.what()
+    };
 }
```

(Assumes `ErrorCode` supports an explanatory string; if not, at least `spdlog::error("...")` or similar.)  



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
        try {
            auto column_descriptor{clp_s::search::ast::ColumnDescriptor::create_from_escaped_tokens(
                    descriptor_tokens,
                    descriptor_namespace
            )};
            column_descriptor->set_matching_types(types);
            if (column_descriptor->is_unresolved_descriptor()
                || column_descriptor->get_descriptor_list().empty())
            {
                return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
            }
            projected_column_to_original_key.emplace(column_descriptor.get(), key);
            projected_columns.emplace_back(std::move(column_descriptor));
        } catch (std::exception const& e) {
            // Preserve root cause for easier debugging
            return ErrorCode{
                ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure,
                /*optional_msg*/ e.what()
            };
        }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T23:05:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,328 @@
+#include "QueryHandlerImpl.hpp"
+
+#include <exception>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/archive_constants.hpp"
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/ConvertToExists.hpp"
+#include "../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../clp_s/search/ast/NarrowTypes.hpp"
+#include "../../../../clp_s/search/ast/OrOfAndForm.hpp"
+#include "../../../../clp_s/search/ast/SearchUtils.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::EmptyExpr;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Pre-processes a search query by applying several transformation passes.
+ * @param query
+ * @return A result containing the transformed
+ * - ErrorCodeEnum::QueryExpressionIsNull if `query` is nullptr.
+ * - ErrorCodeEnum::QueryTransformationPassFailed if any of the transformation pass failed.
+ */
+[[nodiscard]] auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>>;
+
+/**
+ * Creates projected columns and column-to-original-key map from the given projections.
+ * @param projections
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - A vector of projected columns.
+ *   - A projected-column-to-original-key map.
+ * - The possible error codes:
+ *   - ErrorCodeEnum::DuplicateProjectedColumn if `projections` contains duplicated keys.
+ *   - ErrorCodeEnum::ColumnTokenizationFailure if failed to tokenize a projection key.
+ *   - ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure if failed to create a column
+ *     descriptor for the projected key.
+ */
+[[nodiscard]] auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>>;
+
+/**
+ * Creates initial partial resolutions for the given query and projections.
+ * @param query
+ * @param projected_column_to_original_key
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - The partial resolution for auto-generated keys.
+ *   - The partial resolution for user-generated keys.
+ * - The possible error codes:
+ *   - Forwards `is_auto_generated`'s return values.
+ *   - Forwards `initialize_partial_resolution_from_search_ast`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::create`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::next`'s return values.
+ */
+[[nodiscard]] auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>>;
+
+/**
+ * Initializes partial resolutions from a search AST.
+ * @param root The root of the search AST.
+ * @param auto_gen_namespace_partial_resolutions Returns initialized auto-gen partial resolutions.
+ * @param user_gen_namespace_partial_resolutions Returns initialized user-gen partial resolutions.
+ * @return A void result on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::AstDynamicCastFailure if failed to dynamically cast an AST node to a target
+ *   type.
+ * - Forwards `is_auto_generated`'s return values.
+ */
+[[nodiscard]] auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void>;
+
+/**
+ * @param key_namespace
+ * @return A result containing a boolean indicating whether `key_namespace` is auto-generated or
+ * user-generated on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::UnsupportedNamespace if the namespace is not supported.
+ */
+[[nodiscard]] auto is_auto_generated(std::string_view key_namespace)
+        -> outcome_v2::std_result<bool>;
+
+auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>> {
+    if (nullptr == query) {
+        return ErrorCode{ErrorCodeEnum::QueryExpressionIsNull};
+    }
+
+    if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(query)) {
+        return query;
+    }
+
+    try {
+        if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        if (query = clp_s::search::ast::NarrowTypes{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        return clp_s::search::ast::ConvertToExists{}.run(query);
+    } catch (std::exception const& ex) {
+        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+    }
+}
+
+auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>> {
+    std::unordered_set<std::string_view> unique_projected_columns;
+    std::vector<std::shared_ptr<ColumnDescriptor>> projected_columns;
+    QueryHandlerImpl::ProjectionMap projected_column_to_original_key;
+
+    for (auto const& [key, types] : projections) {
+        if (unique_projected_columns.contains(key)) {
+            return ErrorCode{ErrorCodeEnum::DuplicateProjectedColumn};
+        }
+        unique_projected_columns.emplace(key);
+
+        std::vector<std::string> descriptor_tokens;
+        std::string descriptor_namespace;
+        if (false
+            == clp_s::search::ast::tokenize_column_descriptor(
+                    key,
+                    descriptor_tokens,
+                    descriptor_namespace
+            ))
+        {
+            return ErrorCode{ErrorCodeEnum::ColumnTokenizationFailure};
+        }
+
+        try {
+            auto column_descriptor{clp_s::search::ast::ColumnDescriptor::create_from_escaped_tokens(
+                    descriptor_tokens,
+                    descriptor_namespace
+            )};
+            column_descriptor->set_matching_types(types);
+            if (column_descriptor->is_unresolved_descriptor()
+                || column_descriptor->get_descriptor_list().empty())
+            {
+                return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+            }
+            projected_column_to_original_key.emplace(column_descriptor.get(), key);
+            projected_columns.emplace_back(std::move(column_descriptor));
+        } catch (std::exception const& e) {
+            return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+        }
+    }
+
+    return {std::move(projected_columns), std::move(projected_column_to_original_key)};
+}
+
+auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>> {
+    QueryHandlerImpl::PartialResolutionMap auto_gen_namespace_partial_resolutions;
+    QueryHandlerImpl::PartialResolutionMap user_gen_namespace_partial_resolutions;
+    for (auto const& [col, key] : projected_column_to_original_key) {
+        bool const is_auto_gen{OUTCOME_TRYX(is_auto_generated(col->get_namespace()))};
+        auto& partial_resolutions{
+                is_auto_gen ? auto_gen_namespace_partial_resolutions
+                            : user_gen_namespace_partial_resolutions
+        };
+        auto [it, inserted] = partial_resolutions.try_emplace(
+                SchemaTree::cRootId,
+                std::vector<QueryHandlerImpl::ColumnDescriptorTokenIterator>{}
+        );
+        it->second.emplace_back(
+                OUTCOME_TRYX(QueryHandlerImpl::ColumnDescriptorTokenIterator::create(col))
+        );
+    }
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Micro-optimisation: move `try_emplace` outside the projection loop**

`try_emplace(SchemaTree::cRootId, …)` is executed once per projected column even though the key is constant.  
Moving the insertion outside the loop avoids repeated look-ups and slightly simplifies the code:

```diff
-auto_gen_namespace_partial_resolutions.try_emplace(
-        SchemaTree::cRootId,
-        std::vector<QueryHandlerImpl::ColumnDescriptorTokenIterator>{}
-);
-user_gen_namespace_partial_resolutions.try_emplace(
-        SchemaTree::cRootId,
-        std::vector<QueryHandlerImpl::ColumnDescriptorTokenIterator>{}
-);
-for (auto const& [col, key] : projected_column_to_original_key) {
+auto_gen_namespace_partial_resolutions.emplace(
+        SchemaTree::cRootId,
+        std::vector<QueryHandlerImpl::ColumnDescriptorTokenIterator>{}
+);
+user_gen_namespace_partial_resolutions.emplace(
+        SchemaTree::cRootId,
+        std::vector<QueryHandlerImpl::ColumnDescriptorTokenIterator>{}
+);
+for (auto const& [col, key] : projected_column_to_original_key) {
     ...
 }
```

This is admittedly minor, but it removes redundant map operations.  



> Committable suggestion skipped: line range outside the PR's diff.

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T23:05:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,328 @@
+#include "QueryHandlerImpl.hpp"
+
+#include <exception>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/archive_constants.hpp"
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/ConvertToExists.hpp"
+#include "../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../clp_s/search/ast/NarrowTypes.hpp"
+#include "../../../../clp_s/search/ast/OrOfAndForm.hpp"
+#include "../../../../clp_s/search/ast/SearchUtils.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::EmptyExpr;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Pre-processes a search query by applying several transformation passes.
+ * @param query
+ * @return A result containing the transformed
+ * - ErrorCodeEnum::QueryExpressionIsNull if `query` is nullptr.
+ * - ErrorCodeEnum::QueryTransformationPassFailed if any of the transformation pass failed.
+ */
+[[nodiscard]] auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>>;
+
+/**
+ * Creates projected columns and column-to-original-key map from the given projections.
+ * @param projections
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - A vector of projected columns.
+ *   - A projected-column-to-original-key map.
+ * - The possible error codes:
+ *   - ErrorCodeEnum::DuplicateProjectedColumn if `projections` contains duplicated keys.
+ *   - ErrorCodeEnum::ColumnTokenizationFailure if failed to tokenize a projection key.
+ *   - ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure if failed to create a column
+ *     descriptor for the projected key.
+ */
+[[nodiscard]] auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>>;
+
+/**
+ * Creates initial partial resolutions for the given query and projections.
+ * @param query
+ * @param projected_column_to_original_key
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - The partial resolution for auto-generated keys.
+ *   - The partial resolution for user-generated keys.
+ * - The possible error codes:
+ *   - Forwards `is_auto_generated`'s return values.
+ *   - Forwards `initialize_partial_resolution_from_search_ast`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::create`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::next`'s return values.
+ */
+[[nodiscard]] auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>>;
+
+/**
+ * Initializes partial resolutions from a search AST.
+ * @param root The root of the search AST.
+ * @param auto_gen_namespace_partial_resolutions Returns initialized auto-gen partial resolutions.
+ * @param user_gen_namespace_partial_resolutions Returns initialized user-gen partial resolutions.
+ * @return A void result on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::AstDynamicCastFailure if failed to dynamically cast an AST node to a target
+ *   type.
+ * - Forwards `is_auto_generated`'s return values.
+ */
+[[nodiscard]] auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void>;
+
+/**
+ * @param key_namespace
+ * @return A result containing a boolean indicating whether `key_namespace` is auto-generated or
+ * user-generated on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::UnsupportedNamespace if the namespace is not supported.
+ */
+[[nodiscard]] auto is_auto_generated(std::string_view key_namespace)
+        -> outcome_v2::std_result<bool>;
+
+auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>> {
+    if (nullptr == query) {
+        return ErrorCode{ErrorCodeEnum::QueryExpressionIsNull};
+    }
+
+    if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(query)) {
+        return query;
+    }
+
+    try {
+        if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        if (query = clp_s::search::ast::NarrowTypes{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        return clp_s::search::ast::ConvertToExists{}.run(query);
+    } catch (std::exception const& ex) {
+        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+    }
+}
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Granular error mapping improves troubleshooting of query pre-processing**

A single `QueryTransformationPassFailed` masks which of the three passes
(`OrOfAndForm`, `NarrowTypes`, `ConvertToExists`) threw.  Differentiating the
error codes (or adding an enum value per pass) would let callers decide whether
to retry, fall back, or present a precise message to users.

At minimum, wrap each pass in its own `try { … } catch` and translate the
failure separately.

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T23:06:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,328 @@
+#include "QueryHandlerImpl.hpp"
+
+#include <exception>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/archive_constants.hpp"
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/ConvertToExists.hpp"
+#include "../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../clp_s/search/ast/NarrowTypes.hpp"
+#include "../../../../clp_s/search/ast/OrOfAndForm.hpp"
+#include "../../../../clp_s/search/ast/SearchUtils.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::EmptyExpr;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Pre-processes a search query by applying several transformation passes.
+ * @param query
+ * @return A result containing the transformed
+ * - ErrorCodeEnum::QueryExpressionIsNull if `query` is nullptr.
+ * - ErrorCodeEnum::QueryTransformationPassFailed if any of the transformation pass failed.
+ */
+[[nodiscard]] auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>>;
+
+/**
+ * Creates projected columns and column-to-original-key map from the given projections.
+ * @param projections
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - A vector of projected columns.
+ *   - A projected-column-to-original-key map.
+ * - The possible error codes:
+ *   - ErrorCodeEnum::DuplicateProjectedColumn if `projections` contains duplicated keys.
+ *   - ErrorCodeEnum::ColumnTokenizationFailure if failed to tokenize a projection key.
+ *   - ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure if failed to create a column
+ *     descriptor for the projected key.
+ */
+[[nodiscard]] auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>>;
+
+/**
+ * Creates initial partial resolutions for the given query and projections.
+ * @param query
+ * @param projected_column_to_original_key
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - The partial resolution for auto-generated keys.
+ *   - The partial resolution for user-generated keys.
+ * - The possible error codes:
+ *   - Forwards `is_auto_generated`'s return values.
+ *   - Forwards `initialize_partial_resolution_from_search_ast`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::create`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::next`'s return values.
+ */
+[[nodiscard]] auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>>;
+
+/**
+ * Initializes partial resolutions from a search AST.
+ * @param root The root of the search AST.
+ * @param auto_gen_namespace_partial_resolutions Returns initialized auto-gen partial resolutions.
+ * @param user_gen_namespace_partial_resolutions Returns initialized user-gen partial resolutions.
+ * @return A void result on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::AstDynamicCastFailure if failed to dynamically cast an AST node to a target
+ *   type.
+ * - Forwards `is_auto_generated`'s return values.
+ */
+[[nodiscard]] auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void>;
+
+/**
+ * @param key_namespace
+ * @return A result containing a boolean indicating whether `key_namespace` is auto-generated or
+ * user-generated on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::UnsupportedNamespace if the namespace is not supported.
+ */
+[[nodiscard]] auto is_auto_generated(std::string_view key_namespace)
+        -> outcome_v2::std_result<bool>;
+
+auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>> {
+    if (nullptr == query) {
+        return ErrorCode{ErrorCodeEnum::QueryExpressionIsNull};
+    }
+
+    if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(query)) {
+        return query;
+    }
+
+    try {
+        if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        if (query = clp_s::search::ast::NarrowTypes{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        return clp_s::search::ast::ConvertToExists{}.run(query);
+    } catch (std::exception const& ex) {
+        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+    }
+}
+
+auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>> {
+    std::unordered_set<std::string_view> unique_projected_columns;
+    std::vector<std::shared_ptr<ColumnDescriptor>> projected_columns;
+    QueryHandlerImpl::ProjectionMap projected_column_to_original_key;
+
+    for (auto const& [key, types] : projections) {
+        if (unique_projected_columns.contains(key)) {
+            return ErrorCode{ErrorCodeEnum::DuplicateProjectedColumn};
+        }
+        unique_projected_columns.emplace(key);
+
+        std::vector<std::string> descriptor_tokens;
+        std::string descriptor_namespace;
+        if (false
+            == clp_s::search::ast::tokenize_column_descriptor(
+                    key,
+                    descriptor_tokens,
+                    descriptor_namespace
+            ))
+        {
+            return ErrorCode{ErrorCodeEnum::ColumnTokenizationFailure};
+        }
+
+        try {
+            auto column_descriptor{clp_s::search::ast::ColumnDescriptor::create_from_escaped_tokens(
+                    descriptor_tokens,
+                    descriptor_namespace
+            )};
+            column_descriptor->set_matching_types(types);
+            if (column_descriptor->is_unresolved_descriptor()
+                || column_descriptor->get_descriptor_list().empty())
+            {
+                return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+            }
+            projected_column_to_original_key.emplace(column_descriptor.get(), key);
+            projected_columns.emplace_back(std::move(column_descriptor));
+        } catch (std::exception const& e) {
+            return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+        }
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Consider surfacing the original exception message**

The catch-all block swallows `std::exception` details and turns every failure
into the same `ProjectionColumnDescriptorCreationFailure`.  When debugging
tokenisation/descriptor issues, the concrete `ex.what()` is extremely helpful.

```diff
-} catch (std::exception const& e) {
-    return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+} catch (std::exception const& e) {
+    // Option 1: log and re-throw a richer ErrorCode that embeds the message.
+    // Option 2: at least emit the message to a debug log so callers have
+    //           breadcrumbs when a projection mysteriously fails.
+    return ErrorCode{
+        ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure,
+        /* optional_diagnostic = */ e.what()
+    };
```

If `ErrorCode` cannot currently carry diagnostic text, adding such a field (or
a helper `to_string()`) will pay dividends during field investigations.  



> Committable suggestion skipped: line range outside the PR's diff.

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T23:06:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,328 @@
+#include "QueryHandlerImpl.hpp"
+
+#include <exception>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/archive_constants.hpp"
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/ConvertToExists.hpp"
+#include "../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../clp_s/search/ast/NarrowTypes.hpp"
+#include "../../../../clp_s/search/ast/OrOfAndForm.hpp"
+#include "../../../../clp_s/search/ast/SearchUtils.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::EmptyExpr;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Pre-processes a search query by applying several transformation passes.
+ * @param query
+ * @return A result containing the transformed
+ * - ErrorCodeEnum::QueryExpressionIsNull if `query` is nullptr.
+ * - ErrorCodeEnum::QueryTransformationPassFailed if any of the transformation pass failed.
+ */
+[[nodiscard]] auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>>;
+
+/**
+ * Creates projected columns and column-to-original-key map from the given projections.
+ * @param projections
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - A vector of projected columns.
+ *   - A projected-column-to-original-key map.
+ * - The possible error codes:
+ *   - ErrorCodeEnum::DuplicateProjectedColumn if `projections` contains duplicated keys.
+ *   - ErrorCodeEnum::ColumnTokenizationFailure if failed to tokenize a projection key.
+ *   - ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure if failed to create a column
+ *     descriptor for the projected key.
+ */
+[[nodiscard]] auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>>;
+
+/**
+ * Creates initial partial resolutions for the given query and projections.
+ * @param query
+ * @param projected_column_to_original_key
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - The partial resolution for auto-generated keys.
+ *   - The partial resolution for user-generated keys.
+ * - The possible error codes:
+ *   - Forwards `is_auto_generated`'s return values.
+ *   - Forwards `initialize_partial_resolution_from_search_ast`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::create`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::next`'s return values.
+ */
+[[nodiscard]] auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>>;
+
+/**
+ * Initializes partial resolutions from a search AST.
+ * @param root The root of the search AST.
+ * @param auto_gen_namespace_partial_resolutions Returns initialized auto-gen partial resolutions.
+ * @param user_gen_namespace_partial_resolutions Returns initialized user-gen partial resolutions.
+ * @return A void result on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::AstDynamicCastFailure if failed to dynamically cast an AST node to a target
+ *   type.
+ * - Forwards `is_auto_generated`'s return values.
+ */
+[[nodiscard]] auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void>;
+
+/**
+ * @param key_namespace
+ * @return A result containing a boolean indicating whether `key_namespace` is auto-generated or
+ * user-generated on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::UnsupportedNamespace if the namespace is not supported.
+ */
+[[nodiscard]] auto is_auto_generated(std::string_view key_namespace)
+        -> outcome_v2::std_result<bool>;
+
+auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>> {
+    if (nullptr == query) {
+        return ErrorCode{ErrorCodeEnum::QueryExpressionIsNull};
+    }
+
+    if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(query)) {
+        return query;
+    }
+
+    try {
+        if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        if (query = clp_s::search::ast::NarrowTypes{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        return clp_s::search::ast::ConvertToExists{}.run(query);
+    } catch (std::exception const& ex) {
+        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+    }
+}
+
+auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>> {
+    std::unordered_set<std::string_view> unique_projected_columns;
+    std::vector<std::shared_ptr<ColumnDescriptor>> projected_columns;
+    QueryHandlerImpl::ProjectionMap projected_column_to_original_key;
+
+    for (auto const& [key, types] : projections) {
+        if (unique_projected_columns.contains(key)) {
+            return ErrorCode{ErrorCodeEnum::DuplicateProjectedColumn};
+        }
+        unique_projected_columns.emplace(key);
+
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Pre-size containers to avoid repeated re-allocations**

`projections` is already fully materialised, so we know its final size upfront.  
Reserving space for `unique_projected_columns`, `projected_columns`, and
`projected_column_to_original_key` prevents unnecessary growth & re-hash
operations, which becomes noticeable when the caller specifies many projection
keys.

```diff
-std::unordered_set<std::string_view> unique_projected_columns;
-std::vector<std::shared_ptr<ColumnDescriptor>> projected_columns;
-QueryHandlerImpl::ProjectionMap projected_column_to_original_key;
+std::unordered_set<std::string_view> unique_projected_columns;
+unique_projected_columns.reserve(projections.size());
+
+std::vector<std::shared_ptr<ColumnDescriptor>> projected_columns;
+projected_columns.reserve(projections.size());
+
+QueryHandlerImpl::ProjectionMap projected_column_to_original_key;
+projected_column_to_original_key.reserve(projections.size());
```

This is a low-risk micro-optimisation that keeps the current semantics intact.

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T23:06:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,164 @@
+#include "utils.hpp"
+
+#include <bitset>
+#include <cstddef>
+#include <map>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <fmt/core.h>
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../SchemaTree.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::search::ast::LiteralType;
+}  // namespace
+
+auto ColumnQueryPossibleMatches::get_matchable_node_ids_from_literal_type(LiteralType type) const
+        -> std::vector<SchemaTree::Node::id_t> {
+    if ((type & m_matchable_types) == 0) {
+        return {};
+    }
+
+    switch (type) {
+        case LiteralType::IntegerT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Int);
+        case LiteralType::FloatT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Float);
+        case LiteralType::BooleanT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Bool);
+        case LiteralType::VarStringT:
+        case LiteralType::ClpStringT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Str);
+        default:
+            return {};
+    }
+}
+
+auto ColumnQueryPossibleMatches::get_matchable_node_ids_from_schema_tree_type(
+        SchemaTree::Node::Type type
+) const -> std::vector<SchemaTree::Node::id_t> {
+    std::vector<SchemaTree::Node::id_t> node_ids;
+    for (auto const node_id : m_matchable_node_ids) {
+        if (m_schema_tree->get_node(node_id).get_type() == type) {
+            node_ids.emplace_back(node_id);
+        }
+    }
+    return node_ids;
+}
+
+auto ColumnQueryPossibleMatches::get_projectable_node_ids() const
+        -> std::vector<SchemaTree::Node::id_t> {
+    std::vector<SchemaTree::Node::id_t> projectable_node_ids;
+    for (auto const node_id : m_matchable_node_ids) {
+        if (m_schema_tree->get_node(node_id).get_type() != SchemaTree::Node::Type::Obj) {
+            projectable_node_ids.emplace_back(node_id);
+        }
+    }
+    return projectable_node_ids;
+}
+
+auto ColumnQueryPossibleMatches::serialize() const -> std::string {
+    constexpr size_t cBitsetSize{sizeof(m_matchable_types) * 8};
+    return fmt::format(
+            "Types: {}; Node IDs: {{{}}}",
+            std::bitset<cBitsetSize>(m_matchable_types).to_string(),
+            fmt::join(m_matchable_node_ids, ",")
+    );
+}
+
+auto trivial_new_projected_schema_tree_node_callback(
+        [[maybe_unused]] bool is_auto_generated,
+        [[maybe_unused]] SchemaTree::Node::id_t node_id,
+        [[maybe_unused]] std::string_view projected_key_path
+) -> outcome_v2::std_result<void> {
+    return outcome_v2::success();
+}
path: components/core/src/clp/ffi/ir_stream/search/test/utils.cpp
user: login: LinZhihao-723
body: It's been documented in the header file.
created_at: 2025-05-02T23:48:02Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,164 @@
+#include "utils.hpp"
+
+#include <bitset>
+#include <cstddef>
+#include <map>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <fmt/core.h>
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../SchemaTree.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::search::ast::LiteralType;
+}  // namespace
+
+auto ColumnQueryPossibleMatches::get_matchable_node_ids_from_literal_type(LiteralType type) const
+        -> std::vector<SchemaTree::Node::id_t> {
+    if ((type & m_matchable_types) == 0) {
+        return {};
+    }
+
+    switch (type) {
+        case LiteralType::IntegerT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Int);
+        case LiteralType::FloatT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Float);
+        case LiteralType::BooleanT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Bool);
+        case LiteralType::VarStringT:
+        case LiteralType::ClpStringT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Str);
+        default:
+            return {};
+    }
+}
+
+auto ColumnQueryPossibleMatches::get_matchable_node_ids_from_schema_tree_type(
+        SchemaTree::Node::Type type
+) const -> std::vector<SchemaTree::Node::id_t> {
+    std::vector<SchemaTree::Node::id_t> node_ids;
+    for (auto const node_id : m_matchable_node_ids) {
+        if (m_schema_tree->get_node(node_id).get_type() == type) {
+            node_ids.emplace_back(node_id);
+        }
+    }
+    return node_ids;
+}
+
+auto ColumnQueryPossibleMatches::get_projectable_node_ids() const
+        -> std::vector<SchemaTree::Node::id_t> {
+    std::vector<SchemaTree::Node::id_t> projectable_node_ids;
+    for (auto const node_id : m_matchable_node_ids) {
+        if (m_schema_tree->get_node(node_id).get_type() != SchemaTree::Node::Type::Obj) {
+            projectable_node_ids.emplace_back(node_id);
+        }
+    }
+    return projectable_node_ids;
+}
+
+auto ColumnQueryPossibleMatches::serialize() const -> std::string {
+    constexpr size_t cBitsetSize{sizeof(m_matchable_types) * 8};
+    return fmt::format(
+            "Types: {}; Node IDs: {{{}}}",
+            std::bitset<cBitsetSize>(m_matchable_types).to_string(),
+            fmt::join(m_matchable_node_ids, ",")
+    );
+}
+
+auto trivial_new_projected_schema_tree_node_callback(
+        [[maybe_unused]] bool is_auto_generated,
+        [[maybe_unused]] SchemaTree::Node::id_t node_id,
+        [[maybe_unused]] std::string_view projected_key_path
+) -> outcome_v2::std_result<void> {
+    return outcome_v2::success();
+}
path: components/core/src/clp/ffi/ir_stream/search/test/utils.cpp
user: login: coderabbitai[bot]
body: <details>
<summary>🧩 Analysis chain</summary>

After examining the header file, I'll determine if the function is properly documented as claimed.

@LinZhihao-723 Thank you for letting me know. I've checked the documentation in the header file and can see that the function is indeed properly documented there. This should be sufficient to explain its purpose and prevent confusion with static analyzer warnings.



---

🏁 Script executed:
```
#!/bin/bash
# Check the header file for documentation of trivial_new_projected_schema_tree_node_callback
cat "components/core/src/clp/ffi/ir_stream/search/test/utils.hpp" | grep -A 10 -B 10 "trivial_new_projected_schema_tree_node_callback"
```

Length of output: 912

---



</details>

@LinZhihao-723 You're right - I've verified that the function is documented in the header file with the comment:

```cpp
/**
 * Trivial implementation of `NewProjectedSchemaTreeNodeCallback` that always return success without
 * doing anything.
 * @param is_auto_generated
 * @param
 */
```

This documentation explains the purpose of the function, which addresses the static analyzer's concern. Thank you for the clarification!

---

<details>
<summary>🧬 Code Graph Analysis Results</summary>

### Relevant snippets from `components/core/src/clp/ffi/ir_stream/search/test/utils.hpp`

Lines 33-35:
```cpp
get_matchable_node_ids_from_literal_type(
        clp_s::search::ast::LiteralType type
) const -> std::vector<SchemaTree::Node::id_t>
```

Lines 37-39:
```cpp
get_matchable_node_ids_from_schema_tree_type(
        SchemaTree::Node::Type type
) const -> std::vector<SchemaTree::Node::id_t>
```

Lines 43-46:
```cpp
auto set_matchable_node(SchemaTree::Node::id_t node_id, SchemaTree::Node::Type type) -> void {
    m_matchable_node_ids.emplace(node_id);
    m_matchable_types |= schema_tree_node_type_to_literal_types(type);
}
```

Lines 78-81:
```cpp
operator<<(
        std::ostream& os,
        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
) -> std::ostream&
```

---

### Relevant snippet from `components/core/src/clp_s/ArchiveReader.hpp`

Line 116:
```cpp
std::shared_ptr<SchemaTree> get_schema_tree() { return m_schema_tree; }
```

---

### Relevant snippet from `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp`

Line 89:
```cpp
match(SchemaTree::Node::Type type) const -> bool
```

Lines 124-129:
```cpp
create(
        std::shared_ptr<clp_s::search::ast::Expression> query,
        std::vector<std::pair<std::string, clp_s::search::ast::LiteralTypeBitmask>> const& projections,
        bool case_sensitive_match
) -> outcome_v2::std_result<QueryHandlerImpl>
```

---

### Relevant snippet from `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp`

Lines 282-290:
```cpp
auto is_auto_generated(std::string_view key_namespace) -> outcome_v2::std_result<bool> {
    if (clp_s::constants::cAutogenNamespace == key_namespace) {
        return true;
    }
    if (clp_s::constants::cDefaultNamespace == key_namespace) {
        return false;
    }
    return ErrorCode{ErrorCodeEnum::UnsupportedNamespace};
}
```

---

### Summary of relevance

- The header snippets from `utils.hpp` correspond directly to the member functions and operator overloads implemented in `utils.cpp`.
- The `ArchiveReader.hpp` snippet provides access to the `SchemaTree` shared pointer, which is used in `utils.cpp`.
- The `QueryHandlerImpl.hpp` and `.cpp` snippets relate to query handling and node type matching, which may be relevant for understanding the context of `ColumnQueryPossibleMatches` and its interaction with schema tree nodes.
- The `is_auto_generated` function in `QueryHandlerImpl.cpp` is referenced in `utils.cpp` as a trivial callback, indicating relevance for understanding callback usage.

These snippets provide context on types, interfaces, and utility functions related to the code in `utils.cpp`.

</details>



---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: LinZhihao-723
PR: y-scope/clp#873
File: components/core/src/clp/ffi/ir_stream/search/test/utils.cpp:78-84
Timestamp: 2025-05-02T23:49:30.774Z
Learning: The function `trivial_new_projected_schema_tree_node_callback` in the KV-IR codebase is properly documented in the header file to indicate its purpose for testing, which addresses static analyzer warnings about unused functions.
```

---

```
Learnt from: LinZhihao-723
PR: y-scope/clp#873
File: components/core/src/clp/ffi/ir_stream/search/test/utils.cpp:78-84
Timestamp: 2025-05-02T23:49:30.774Z
Learning: The function `trivial_new_projected_schema_tree_node_callback` in the KV-IR codebase is documented in the header file to explain its purpose for testing, which addresses static analyzer warnings about unused functions.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>




<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer><details>
<summary>🧩 Analysis chain</summary>

After examining the header file, I'll determine if the function is properly documented as claimed.

@LinZhihao-723 Thank you for letting me know. I've checked the documentation in the header file and can see that the function is indeed properly documented there. This should be sufficient to explain its purpose and prevent confusion with static analyzer warnings.



---

🏁 Script executed:
```
#!/bin/bash
# Check the header file for documentation of trivial_new_projected_schema_tree_node_callback
cat "components/core/src/clp/ffi/ir_stream/search/test/utils.hpp" | grep -A 10 -B 10 "trivial_new_projected_schema_tree_node_callback"
```

Length of output: 912

---



</details>

@LinZhihao-723 You're right - I've verified that the function is documented in the header file with the comment:

```cpp
/**
 * Trivial implementation of `NewProjectedSchemaTreeNodeCallback` that always return success without
 * doing anything.
 * @param is_auto_generated
 * @param
 */
```

This documentation explains the purpose of the function, which addresses the static analyzer's concern. Thank you for the clarification!

---

<details>
<summary>🧬 Code Graph Analysis Results</summary>

### Relevant snippets from `components/core/src/clp/ffi/ir_stream/search/test/utils.hpp`

Lines 33-35:
```cpp
get_matchable_node_ids_from_literal_type(
        clp_s::search::ast::LiteralType type
) const -> std::vector<SchemaTree::Node::id_t>
```

Lines 37-39:
```cpp
get_matchable_node_ids_from_schema_tree_type(
        SchemaTree::Node::Type type
) const -> std::vector<SchemaTree::Node::id_t>
```

Lines 43-46:
```cpp
auto set_matchable_node(SchemaTree::Node::id_t node_id, SchemaTree::Node::Type type) -> void {
    m_matchable_node_ids.emplace(node_id);
    m_matchable_types |= schema_tree_node_type_to_literal_types(type);
}
```

Lines 78-81:
```cpp
operator<<(
        std::ostream& os,
        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
) -> std::ostream&
```

---

### Relevant snippet from `components/core/src/clp_s/ArchiveReader.hpp`

Line 116:
```cpp
std::shared_ptr<SchemaTree> get_schema_tree() { return m_schema_tree; }
```

---

### Relevant snippet from `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp`

Line 89:
```cpp
match(SchemaTree::Node::Type type) const -> bool
```

Lines 124-129:
```cpp
create(
        std::shared_ptr<clp_s::search::ast::Expression> query,
        std::vector<std::pair<std::string, clp_s::search::ast::LiteralTypeBitmask>> const& projections,
        bool case_sensitive_match
) -> outcome_v2::std_result<QueryHandlerImpl>
```

---

### Relevant snippet from `components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp`

Lines 282-290:
```cpp
auto is_auto_generated(std::string_view key_namespace) -> outcome_v2::std_result<bool> {
    if (clp_s::constants::cAutogenNamespace == key_namespace) {
        return true;
    }
    if (clp_s::constants::cDefaultNamespace == key_namespace) {
        return false;
    }
    return ErrorCode{ErrorCodeEnum::UnsupportedNamespace};
}
```

---

### Summary of relevance

- The header snippets from `utils.hpp` correspond directly to the member functions and operator overloads implemented in `utils.cpp`.
- The `ArchiveReader.hpp` snippet provides access to the `SchemaTree` shared pointer, which is used in `utils.cpp`.
- The `QueryHandlerImpl.hpp` and `.cpp` snippets relate to query handling and node type matching, which may be relevant for understanding the context of `ColumnQueryPossibleMatches` and its interaction with schema tree nodes.
- The `is_auto_generated` function in `QueryHandlerImpl.cpp` is referenced in `utils.cpp` as a trivial callback, indicating relevance for understanding callback usage.

These snippets provide context on types, interfaces, and utility functions related to the code in `utils.cpp`.

</details>



---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: LinZhihao-723
PR: y-scope/clp#873
File: components/core/src/clp/ffi/ir_stream/search/test/utils.cpp:78-84
Timestamp: 2025-05-02T23:49:30.774Z
Learning: The function `trivial_new_projected_schema_tree_node_callback` in the KV-IR codebase is properly documented in the header file to indicate its purpose for testing, which addresses static analyzer warnings about unused functions.
```

---

```
Learnt from: LinZhihao-723
PR: y-scope/clp#873
File: components/core/src/clp/ffi/ir_stream/search/test/utils.cpp:78-84
Timestamp: 2025-05-02T23:49:30.774Z
Learning: The function `trivial_new_projected_schema_tree_node_callback` in the KV-IR codebase is documented in the header file to explain its purpose for testing, which addresses static analyzer warnings about unused functions.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>

</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T23:49:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,328 @@
+#include "QueryHandlerImpl.hpp"
+
+#include <exception>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/archive_constants.hpp"
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/ConvertToExists.hpp"
+#include "../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../clp_s/search/ast/NarrowTypes.hpp"
+#include "../../../../clp_s/search/ast/OrOfAndForm.hpp"
+#include "../../../../clp_s/search/ast/SearchUtils.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::EmptyExpr;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Pre-processes a search query by applying several transformation passes.
+ * @param query
+ * @return A result containing the transformed
+ * - ErrorCodeEnum::QueryExpressionIsNull if `query` is nullptr.
+ * - ErrorCodeEnum::QueryTransformationPassFailed if any of the transformation pass failed.
+ */
+[[nodiscard]] auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>>;
+
+/**
+ * Creates projected columns and column-to-original-key map from the given projections.
+ * @param projections
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - A vector of projected columns.
+ *   - A projected-column-to-original-key map.
+ * - The possible error codes:
+ *   - ErrorCodeEnum::DuplicateProjectedColumn if `projections` contains duplicated keys.
+ *   - ErrorCodeEnum::ColumnTokenizationFailure if failed to tokenize a projection key.
+ *   - ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure if failed to create a column
+ *     descriptor for the projected key.
+ */
+[[nodiscard]] auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>>;
+
+/**
+ * Creates initial partial resolutions for the given query and projections.
+ * @param query
+ * @param projected_column_to_original_key
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - The partial resolution for auto-generated keys.
+ *   - The partial resolution for user-generated keys.
+ * - The possible error codes:
+ *   - Forwards `is_auto_generated`'s return values.
+ *   - Forwards `initialize_partial_resolution_from_search_ast`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::create`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::next`'s return values.
+ */
+[[nodiscard]] auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>>;
+
+/**
+ * Initializes partial resolutions from a search AST.
+ * @param root The root of the search AST.
+ * @param auto_gen_namespace_partial_resolutions Returns initialized auto-gen partial resolutions.
+ * @param user_gen_namespace_partial_resolutions Returns initialized user-gen partial resolutions.
+ * @return A void result on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::AstDynamicCastFailure if failed to dynamically cast an AST node to a target
+ *   type.
+ * - Forwards `is_auto_generated`'s return values.
+ */
+[[nodiscard]] auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void>;
+
+/**
+ * @param key_namespace
+ * @return A result containing a boolean indicating whether `key_namespace` is auto-generated or
+ * user-generated on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::UnsupportedNamespace if the namespace is not supported.
+ */
+[[nodiscard]] auto is_auto_generated(std::string_view key_namespace)
+        -> outcome_v2::std_result<bool>;
+
+auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>> {
+    if (nullptr == query) {
+        return ErrorCode{ErrorCodeEnum::QueryExpressionIsNull};
+    }
+
+    if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(query)) {
+        return query;
+    }
+
+    try {
+        if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        if (query = clp_s::search::ast::NarrowTypes{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        return clp_s::search::ast::ConvertToExists{}.run(query);
+    } catch (std::exception const& ex) {
+        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+    }
+}
+
+auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>> {
+    std::unordered_set<std::string_view> unique_projected_columns;
+    std::vector<std::shared_ptr<ColumnDescriptor>> projected_columns;
+    QueryHandlerImpl::ProjectionMap projected_column_to_original_key;
+
+    for (auto const& [key, types] : projections) {
+        if (unique_projected_columns.contains(key)) {
+            return ErrorCode{ErrorCodeEnum::DuplicateProjectedColumn};
+        }
+        unique_projected_columns.emplace(key);
+
+        std::vector<std::string> descriptor_tokens;
+        std::string descriptor_namespace;
+        if (false
+            == clp_s::search::ast::tokenize_column_descriptor(
+                    key,
+                    descriptor_tokens,
+                    descriptor_namespace
+            ))
+        {
+            return ErrorCode{ErrorCodeEnum::ColumnTokenizationFailure};
+        }
+
+        try {
+            auto column_descriptor{clp_s::search::ast::ColumnDescriptor::create_from_escaped_tokens(
+                    descriptor_tokens,
+                    descriptor_namespace
+            )};
+            column_descriptor->set_matching_types(types);
+            if (column_descriptor->is_unresolved_descriptor()
+                || column_descriptor->get_descriptor_list().empty())
+            {
+                return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+            }
+            projected_column_to_original_key.emplace(column_descriptor.get(), key);
+            projected_columns.emplace_back(std::move(column_descriptor));
+        } catch (std::exception const& e) {
+            return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+        }
+    }
+
+    return {std::move(projected_columns), std::move(projected_column_to_original_key)};
+}
+
+auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>> {
+    QueryHandlerImpl::PartialResolutionMap auto_gen_namespace_partial_resolutions;
+    QueryHandlerImpl::PartialResolutionMap user_gen_namespace_partial_resolutions;
+    for (auto const& [col, key] : projected_column_to_original_key) {
+        bool const is_auto_gen{OUTCOME_TRYX(is_auto_generated(col->get_namespace()))};
+        auto& partial_resolutions{
+                is_auto_gen ? auto_gen_namespace_partial_resolutions
+                            : user_gen_namespace_partial_resolutions
+        };
+        auto [it, inserted] = partial_resolutions.try_emplace(
+                SchemaTree::cRootId,
+                std::vector<QueryHandlerImpl::ColumnDescriptorTokenIterator>{}
+        );
+        it->second.emplace_back(
+                OUTCOME_TRYX(QueryHandlerImpl::ColumnDescriptorTokenIterator::create(col))
+        );
+    }
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
user: login: LinZhihao-723
body: prefer to write in this way since there might be no projected keys.
created_at: 2025-05-02T23:51:54Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,328 @@
+#include "QueryHandlerImpl.hpp"
+
+#include <exception>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/archive_constants.hpp"
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/ConvertToExists.hpp"
+#include "../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../clp_s/search/ast/NarrowTypes.hpp"
+#include "../../../../clp_s/search/ast/OrOfAndForm.hpp"
+#include "../../../../clp_s/search/ast/SearchUtils.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::EmptyExpr;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Pre-processes a search query by applying several transformation passes.
+ * @param query
+ * @return A result containing the transformed
+ * - ErrorCodeEnum::QueryExpressionIsNull if `query` is nullptr.
+ * - ErrorCodeEnum::QueryTransformationPassFailed if any of the transformation pass failed.
+ */
+[[nodiscard]] auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>>;
+
+/**
+ * Creates projected columns and column-to-original-key map from the given projections.
+ * @param projections
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - A vector of projected columns.
+ *   - A projected-column-to-original-key map.
+ * - The possible error codes:
+ *   - ErrorCodeEnum::DuplicateProjectedColumn if `projections` contains duplicated keys.
+ *   - ErrorCodeEnum::ColumnTokenizationFailure if failed to tokenize a projection key.
+ *   - ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure if failed to create a column
+ *     descriptor for the projected key.
+ */
+[[nodiscard]] auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>>;
+
+/**
+ * Creates initial partial resolutions for the given query and projections.
+ * @param query
+ * @param projected_column_to_original_key
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - The partial resolution for auto-generated keys.
+ *   - The partial resolution for user-generated keys.
+ * - The possible error codes:
+ *   - Forwards `is_auto_generated`'s return values.
+ *   - Forwards `initialize_partial_resolution_from_search_ast`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::create`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::next`'s return values.
+ */
+[[nodiscard]] auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>>;
+
+/**
+ * Initializes partial resolutions from a search AST.
+ * @param root The root of the search AST.
+ * @param auto_gen_namespace_partial_resolutions Returns initialized auto-gen partial resolutions.
+ * @param user_gen_namespace_partial_resolutions Returns initialized user-gen partial resolutions.
+ * @return A void result on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::AstDynamicCastFailure if failed to dynamically cast an AST node to a target
+ *   type.
+ * - Forwards `is_auto_generated`'s return values.
+ */
+[[nodiscard]] auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void>;
+
+/**
+ * @param key_namespace
+ * @return A result containing a boolean indicating whether `key_namespace` is auto-generated or
+ * user-generated on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::UnsupportedNamespace if the namespace is not supported.
+ */
+[[nodiscard]] auto is_auto_generated(std::string_view key_namespace)
+        -> outcome_v2::std_result<bool>;
+
+auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>> {
+    if (nullptr == query) {
+        return ErrorCode{ErrorCodeEnum::QueryExpressionIsNull};
+    }
+
+    if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(query)) {
+        return query;
+    }
+
+    try {
+        if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        if (query = clp_s::search::ast::NarrowTypes{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        return clp_s::search::ast::ConvertToExists{}.run(query);
+    } catch (std::exception const& ex) {
+        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+    }
+}
+
+auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>> {
+    std::unordered_set<std::string_view> unique_projected_columns;
+    std::vector<std::shared_ptr<ColumnDescriptor>> projected_columns;
+    QueryHandlerImpl::ProjectionMap projected_column_to_original_key;
+
+    for (auto const& [key, types] : projections) {
+        if (unique_projected_columns.contains(key)) {
+            return ErrorCode{ErrorCodeEnum::DuplicateProjectedColumn};
+        }
+        unique_projected_columns.emplace(key);
+
+        std::vector<std::string> descriptor_tokens;
+        std::string descriptor_namespace;
+        if (false
+            == clp_s::search::ast::tokenize_column_descriptor(
+                    key,
+                    descriptor_tokens,
+                    descriptor_namespace
+            ))
+        {
+            return ErrorCode{ErrorCodeEnum::ColumnTokenizationFailure};
+        }
+
+        try {
+            auto column_descriptor{clp_s::search::ast::ColumnDescriptor::create_from_escaped_tokens(
+                    descriptor_tokens,
+                    descriptor_namespace
+            )};
+            column_descriptor->set_matching_types(types);
+            if (column_descriptor->is_unresolved_descriptor()
+                || column_descriptor->get_descriptor_list().empty())
+            {
+                return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+            }
+            projected_column_to_original_key.emplace(column_descriptor.get(), key);
+            projected_columns.emplace_back(std::move(column_descriptor));
+        } catch (std::exception const& e) {
+            return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+        }
+    }
+
+    return {std::move(projected_columns), std::move(projected_column_to_original_key)};
+}
+
+auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>> {
+    QueryHandlerImpl::PartialResolutionMap auto_gen_namespace_partial_resolutions;
+    QueryHandlerImpl::PartialResolutionMap user_gen_namespace_partial_resolutions;
+    for (auto const& [col, key] : projected_column_to_original_key) {
+        bool const is_auto_gen{OUTCOME_TRYX(is_auto_generated(col->get_namespace()))};
+        auto& partial_resolutions{
+                is_auto_gen ? auto_gen_namespace_partial_resolutions
+                            : user_gen_namespace_partial_resolutions
+        };
+        auto [it, inserted] = partial_resolutions.try_emplace(
+                SchemaTree::cRootId,
+                std::vector<QueryHandlerImpl::ColumnDescriptorTokenIterator>{}
+        );
+        it->second.emplace_back(
+                OUTCOME_TRYX(QueryHandlerImpl::ColumnDescriptorTokenIterator::create(col))
+        );
+    }
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: @LinZhihao-723 I understand your reasoning. Your approach is more semantically precise - it only creates map entries when there are actually projected keys to add. This is a valid design choice that avoids creating unnecessary empty collections.

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: LinZhihao-723
PR: y-scope/clp#873
File: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp:200-213
Timestamp: 2025-05-02T23:52:24.268Z
Learning: In the CLP codebase, when adding elements to a collection inside a loop, it's preferred to use `try_emplace` inside the loop (rather than initializing collections outside the loop) to avoid creating empty collections when there might be no elements to add.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>




<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@LinZhihao-723 I understand your reasoning. Your approach is more semantically precise - it only creates map entries when there are actually projected keys to add. This is a valid design choice that avoids creating unnecessary empty collections.

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: LinZhihao-723
PR: y-scope/clp#873
File: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp:200-213
Timestamp: 2025-05-02T23:52:24.268Z
Learning: In the CLP codebase, when adding elements to a collection inside a loop, it's preferred to use `try_emplace` inside the loop (rather than initializing collections outside the loop) to avoid creating empty collections when there might be no elements to add.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>

</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T23:52:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,328 @@
+#include "QueryHandlerImpl.hpp"
+
+#include <exception>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/archive_constants.hpp"
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/ConvertToExists.hpp"
+#include "../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../clp_s/search/ast/NarrowTypes.hpp"
+#include "../../../../clp_s/search/ast/OrOfAndForm.hpp"
+#include "../../../../clp_s/search/ast/SearchUtils.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::EmptyExpr;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Pre-processes a search query by applying several transformation passes.
+ * @param query
+ * @return A result containing the transformed
+ * - ErrorCodeEnum::QueryExpressionIsNull if `query` is nullptr.
+ * - ErrorCodeEnum::QueryTransformationPassFailed if any of the transformation pass failed.
+ */
+[[nodiscard]] auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>>;
+
+/**
+ * Creates projected columns and column-to-original-key map from the given projections.
+ * @param projections
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - A vector of projected columns.
+ *   - A projected-column-to-original-key map.
+ * - The possible error codes:
+ *   - ErrorCodeEnum::DuplicateProjectedColumn if `projections` contains duplicated keys.
+ *   - ErrorCodeEnum::ColumnTokenizationFailure if failed to tokenize a projection key.
+ *   - ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure if failed to create a column
+ *     descriptor for the projected key.
+ */
+[[nodiscard]] auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>>;
+
+/**
+ * Creates initial partial resolutions for the given query and projections.
+ * @param query
+ * @param projected_column_to_original_key
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - The partial resolution for auto-generated keys.
+ *   - The partial resolution for user-generated keys.
+ * - The possible error codes:
+ *   - Forwards `is_auto_generated`'s return values.
+ *   - Forwards `initialize_partial_resolution_from_search_ast`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::create`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::next`'s return values.
+ */
+[[nodiscard]] auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>>;
+
+/**
+ * Initializes partial resolutions from a search AST.
+ * @param root The root of the search AST.
+ * @param auto_gen_namespace_partial_resolutions Returns initialized auto-gen partial resolutions.
+ * @param user_gen_namespace_partial_resolutions Returns initialized user-gen partial resolutions.
+ * @return A void result on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::AstDynamicCastFailure if failed to dynamically cast an AST node to a target
+ *   type.
+ * - Forwards `is_auto_generated`'s return values.
+ */
+[[nodiscard]] auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void>;
+
+/**
+ * @param key_namespace
+ * @return A result containing a boolean indicating whether `key_namespace` is auto-generated or
+ * user-generated on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::UnsupportedNamespace if the namespace is not supported.
+ */
+[[nodiscard]] auto is_auto_generated(std::string_view key_namespace)
+        -> outcome_v2::std_result<bool>;
+
+auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>> {
+    if (nullptr == query) {
+        return ErrorCode{ErrorCodeEnum::QueryExpressionIsNull};
+    }
+
+    if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(query)) {
+        return query;
+    }
+
+    try {
+        if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        if (query = clp_s::search::ast::NarrowTypes{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        return clp_s::search::ast::ConvertToExists{}.run(query);
+    } catch (std::exception const& ex) {
+        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+    }
+}
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
user: login: LinZhihao-723
body: I don't think it's necessary...
created_at: 2025-05-02T23:56:14Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,328 @@
+#include "QueryHandlerImpl.hpp"
+
+#include <exception>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/archive_constants.hpp"
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/ConvertToExists.hpp"
+#include "../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../clp_s/search/ast/NarrowTypes.hpp"
+#include "../../../../clp_s/search/ast/OrOfAndForm.hpp"
+#include "../../../../clp_s/search/ast/SearchUtils.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::EmptyExpr;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Pre-processes a search query by applying several transformation passes.
+ * @param query
+ * @return A result containing the transformed
+ * - ErrorCodeEnum::QueryExpressionIsNull if `query` is nullptr.
+ * - ErrorCodeEnum::QueryTransformationPassFailed if any of the transformation pass failed.
+ */
+[[nodiscard]] auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>>;
+
+/**
+ * Creates projected columns and column-to-original-key map from the given projections.
+ * @param projections
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - A vector of projected columns.
+ *   - A projected-column-to-original-key map.
+ * - The possible error codes:
+ *   - ErrorCodeEnum::DuplicateProjectedColumn if `projections` contains duplicated keys.
+ *   - ErrorCodeEnum::ColumnTokenizationFailure if failed to tokenize a projection key.
+ *   - ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure if failed to create a column
+ *     descriptor for the projected key.
+ */
+[[nodiscard]] auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>>;
+
+/**
+ * Creates initial partial resolutions for the given query and projections.
+ * @param query
+ * @param projected_column_to_original_key
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - The partial resolution for auto-generated keys.
+ *   - The partial resolution for user-generated keys.
+ * - The possible error codes:
+ *   - Forwards `is_auto_generated`'s return values.
+ *   - Forwards `initialize_partial_resolution_from_search_ast`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::create`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::next`'s return values.
+ */
+[[nodiscard]] auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>>;
+
+/**
+ * Initializes partial resolutions from a search AST.
+ * @param root The root of the search AST.
+ * @param auto_gen_namespace_partial_resolutions Returns initialized auto-gen partial resolutions.
+ * @param user_gen_namespace_partial_resolutions Returns initialized user-gen partial resolutions.
+ * @return A void result on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::AstDynamicCastFailure if failed to dynamically cast an AST node to a target
+ *   type.
+ * - Forwards `is_auto_generated`'s return values.
+ */
+[[nodiscard]] auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void>;
+
+/**
+ * @param key_namespace
+ * @return A result containing a boolean indicating whether `key_namespace` is auto-generated or
+ * user-generated on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::UnsupportedNamespace if the namespace is not supported.
+ */
+[[nodiscard]] auto is_auto_generated(std::string_view key_namespace)
+        -> outcome_v2::std_result<bool>;
+
+auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>> {
+    if (nullptr == query) {
+        return ErrorCode{ErrorCodeEnum::QueryExpressionIsNull};
+    }
+
+    if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(query)) {
+        return query;
+    }
+
+    try {
+        if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        if (query = clp_s::search::ast::NarrowTypes{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        return clp_s::search::ast::ConvertToExists{}.run(query);
+    } catch (std::exception const& ex) {
+        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+    }
+}
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: @LinZhihao-723 Fair enough, that's your call. Thanks for considering the suggestion.


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@LinZhihao-723 Fair enough, that's your call. Thanks for considering the suggestion.</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T23:56:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,328 @@
+#include "QueryHandlerImpl.hpp"
+
+#include <exception>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/archive_constants.hpp"
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/ConvertToExists.hpp"
+#include "../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../clp_s/search/ast/NarrowTypes.hpp"
+#include "../../../../clp_s/search/ast/OrOfAndForm.hpp"
+#include "../../../../clp_s/search/ast/SearchUtils.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::EmptyExpr;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Pre-processes a search query by applying several transformation passes.
+ * @param query
+ * @return A result containing the transformed
+ * - ErrorCodeEnum::QueryExpressionIsNull if `query` is nullptr.
+ * - ErrorCodeEnum::QueryTransformationPassFailed if any of the transformation pass failed.
+ */
+[[nodiscard]] auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>>;
+
+/**
+ * Creates projected columns and column-to-original-key map from the given projections.
+ * @param projections
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - A vector of projected columns.
+ *   - A projected-column-to-original-key map.
+ * - The possible error codes:
+ *   - ErrorCodeEnum::DuplicateProjectedColumn if `projections` contains duplicated keys.
+ *   - ErrorCodeEnum::ColumnTokenizationFailure if failed to tokenize a projection key.
+ *   - ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure if failed to create a column
+ *     descriptor for the projected key.
+ */
+[[nodiscard]] auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>>;
+
+/**
+ * Creates initial partial resolutions for the given query and projections.
+ * @param query
+ * @param projected_column_to_original_key
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - The partial resolution for auto-generated keys.
+ *   - The partial resolution for user-generated keys.
+ * - The possible error codes:
+ *   - Forwards `is_auto_generated`'s return values.
+ *   - Forwards `initialize_partial_resolution_from_search_ast`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::create`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::next`'s return values.
+ */
+[[nodiscard]] auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>>;
+
+/**
+ * Initializes partial resolutions from a search AST.
+ * @param root The root of the search AST.
+ * @param auto_gen_namespace_partial_resolutions Returns initialized auto-gen partial resolutions.
+ * @param user_gen_namespace_partial_resolutions Returns initialized user-gen partial resolutions.
+ * @return A void result on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::AstDynamicCastFailure if failed to dynamically cast an AST node to a target
+ *   type.
+ * - Forwards `is_auto_generated`'s return values.
+ */
+[[nodiscard]] auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void>;
+
+/**
+ * @param key_namespace
+ * @return A result containing a boolean indicating whether `key_namespace` is auto-generated or
+ * user-generated on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::UnsupportedNamespace if the namespace is not supported.
+ */
+[[nodiscard]] auto is_auto_generated(std::string_view key_namespace)
+        -> outcome_v2::std_result<bool>;
+
+auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>> {
+    if (nullptr == query) {
+        return ErrorCode{ErrorCodeEnum::QueryExpressionIsNull};
+    }
+
+    if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(query)) {
+        return query;
+    }
+
+    try {
+        if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        if (query = clp_s::search::ast::NarrowTypes{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        return clp_s::search::ast::ConvertToExists{}.run(query);
+    } catch (std::exception const& ex) {
+        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+    }
+}
+
+auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>> {
+    std::unordered_set<std::string_view> unique_projected_columns;
+    std::vector<std::shared_ptr<ColumnDescriptor>> projected_columns;
+    QueryHandlerImpl::ProjectionMap projected_column_to_original_key;
+
+    for (auto const& [key, types] : projections) {
+        if (unique_projected_columns.contains(key)) {
+            return ErrorCode{ErrorCodeEnum::DuplicateProjectedColumn};
+        }
+        unique_projected_columns.emplace(key);
+
+        std::vector<std::string> descriptor_tokens;
+        std::string descriptor_namespace;
+        if (false
+            == clp_s::search::ast::tokenize_column_descriptor(
+                    key,
+                    descriptor_tokens,
+                    descriptor_namespace
+            ))
+        {
+            return ErrorCode{ErrorCodeEnum::ColumnTokenizationFailure};
+        }
+
+        try {
+            auto column_descriptor{clp_s::search::ast::ColumnDescriptor::create_from_escaped_tokens(
+                    descriptor_tokens,
+                    descriptor_namespace
+            )};
+            column_descriptor->set_matching_types(types);
+            if (column_descriptor->is_unresolved_descriptor()
+                || column_descriptor->get_descriptor_list().empty())
+            {
+                return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+            }
+            projected_column_to_original_key.emplace(column_descriptor.get(), key);
+            projected_columns.emplace_back(std::move(column_descriptor));
+        } catch (std::exception const& e) {
+            return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+        }
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
user: login: LinZhihao-723
body: Returning the error code at this moment should be sufficient.
created_at: 2025-05-02T23:56:41Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,328 @@
+#include "QueryHandlerImpl.hpp"
+
+#include <exception>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/archive_constants.hpp"
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/ConvertToExists.hpp"
+#include "../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../clp_s/search/ast/NarrowTypes.hpp"
+#include "../../../../clp_s/search/ast/OrOfAndForm.hpp"
+#include "../../../../clp_s/search/ast/SearchUtils.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::EmptyExpr;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Pre-processes a search query by applying several transformation passes.
+ * @param query
+ * @return A result containing the transformed
+ * - ErrorCodeEnum::QueryExpressionIsNull if `query` is nullptr.
+ * - ErrorCodeEnum::QueryTransformationPassFailed if any of the transformation pass failed.
+ */
+[[nodiscard]] auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>>;
+
+/**
+ * Creates projected columns and column-to-original-key map from the given projections.
+ * @param projections
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - A vector of projected columns.
+ *   - A projected-column-to-original-key map.
+ * - The possible error codes:
+ *   - ErrorCodeEnum::DuplicateProjectedColumn if `projections` contains duplicated keys.
+ *   - ErrorCodeEnum::ColumnTokenizationFailure if failed to tokenize a projection key.
+ *   - ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure if failed to create a column
+ *     descriptor for the projected key.
+ */
+[[nodiscard]] auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>>;
+
+/**
+ * Creates initial partial resolutions for the given query and projections.
+ * @param query
+ * @param projected_column_to_original_key
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - The partial resolution for auto-generated keys.
+ *   - The partial resolution for user-generated keys.
+ * - The possible error codes:
+ *   - Forwards `is_auto_generated`'s return values.
+ *   - Forwards `initialize_partial_resolution_from_search_ast`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::create`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::next`'s return values.
+ */
+[[nodiscard]] auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>>;
+
+/**
+ * Initializes partial resolutions from a search AST.
+ * @param root The root of the search AST.
+ * @param auto_gen_namespace_partial_resolutions Returns initialized auto-gen partial resolutions.
+ * @param user_gen_namespace_partial_resolutions Returns initialized user-gen partial resolutions.
+ * @return A void result on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::AstDynamicCastFailure if failed to dynamically cast an AST node to a target
+ *   type.
+ * - Forwards `is_auto_generated`'s return values.
+ */
+[[nodiscard]] auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void>;
+
+/**
+ * @param key_namespace
+ * @return A result containing a boolean indicating whether `key_namespace` is auto-generated or
+ * user-generated on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::UnsupportedNamespace if the namespace is not supported.
+ */
+[[nodiscard]] auto is_auto_generated(std::string_view key_namespace)
+        -> outcome_v2::std_result<bool>;
+
+auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>> {
+    if (nullptr == query) {
+        return ErrorCode{ErrorCodeEnum::QueryExpressionIsNull};
+    }
+
+    if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(query)) {
+        return query;
+    }
+
+    try {
+        if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        if (query = clp_s::search::ast::NarrowTypes{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        return clp_s::search::ast::ConvertToExists{}.run(query);
+    } catch (std::exception const& ex) {
+        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+    }
+}
+
+auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>> {
+    std::unordered_set<std::string_view> unique_projected_columns;
+    std::vector<std::shared_ptr<ColumnDescriptor>> projected_columns;
+    QueryHandlerImpl::ProjectionMap projected_column_to_original_key;
+
+    for (auto const& [key, types] : projections) {
+        if (unique_projected_columns.contains(key)) {
+            return ErrorCode{ErrorCodeEnum::DuplicateProjectedColumn};
+        }
+        unique_projected_columns.emplace(key);
+
+        std::vector<std::string> descriptor_tokens;
+        std::string descriptor_namespace;
+        if (false
+            == clp_s::search::ast::tokenize_column_descriptor(
+                    key,
+                    descriptor_tokens,
+                    descriptor_namespace
+            ))
+        {
+            return ErrorCode{ErrorCodeEnum::ColumnTokenizationFailure};
+        }
+
+        try {
+            auto column_descriptor{clp_s::search::ast::ColumnDescriptor::create_from_escaped_tokens(
+                    descriptor_tokens,
+                    descriptor_namespace
+            )};
+            column_descriptor->set_matching_types(types);
+            if (column_descriptor->is_unresolved_descriptor()
+                || column_descriptor->get_descriptor_list().empty())
+            {
+                return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+            }
+            projected_column_to_original_key.emplace(column_descriptor.get(), key);
+            projected_columns.emplace_back(std::move(column_descriptor));
+        } catch (std::exception const& e) {
+            return ErrorCode{ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure};
+        }
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: @LinZhihao-723 Understood! I'll respect your decision to keep the simpler error handling approach with just the error code. Thanks for considering the suggestion.

ʕ•ᴥ•ʔ


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@LinZhihao-723 Understood! I'll respect your decision to keep the simpler error handling approach with just the error code. Thanks for considering the suggestion.

ʕ•ᴥ•ʔ</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T23:57:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,328 @@
+#include "QueryHandlerImpl.hpp"
+
+#include <exception>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/archive_constants.hpp"
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/ConvertToExists.hpp"
+#include "../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../clp_s/search/ast/NarrowTypes.hpp"
+#include "../../../../clp_s/search/ast/OrOfAndForm.hpp"
+#include "../../../../clp_s/search/ast/SearchUtils.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::EmptyExpr;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Pre-processes a search query by applying several transformation passes.
+ * @param query
+ * @return A result containing the transformed
+ * - ErrorCodeEnum::QueryExpressionIsNull if `query` is nullptr.
+ * - ErrorCodeEnum::QueryTransformationPassFailed if any of the transformation pass failed.
+ */
+[[nodiscard]] auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>>;
+
+/**
+ * Creates projected columns and column-to-original-key map from the given projections.
+ * @param projections
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - A vector of projected columns.
+ *   - A projected-column-to-original-key map.
+ * - The possible error codes:
+ *   - ErrorCodeEnum::DuplicateProjectedColumn if `projections` contains duplicated keys.
+ *   - ErrorCodeEnum::ColumnTokenizationFailure if failed to tokenize a projection key.
+ *   - ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure if failed to create a column
+ *     descriptor for the projected key.
+ */
+[[nodiscard]] auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>>;
+
+/**
+ * Creates initial partial resolutions for the given query and projections.
+ * @param query
+ * @param projected_column_to_original_key
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - The partial resolution for auto-generated keys.
+ *   - The partial resolution for user-generated keys.
+ * - The possible error codes:
+ *   - Forwards `is_auto_generated`'s return values.
+ *   - Forwards `initialize_partial_resolution_from_search_ast`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::create`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::next`'s return values.
+ */
+[[nodiscard]] auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>>;
+
+/**
+ * Initializes partial resolutions from a search AST.
+ * @param root The root of the search AST.
+ * @param auto_gen_namespace_partial_resolutions Returns initialized auto-gen partial resolutions.
+ * @param user_gen_namespace_partial_resolutions Returns initialized user-gen partial resolutions.
+ * @return A void result on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::AstDynamicCastFailure if failed to dynamically cast an AST node to a target
+ *   type.
+ * - Forwards `is_auto_generated`'s return values.
+ */
+[[nodiscard]] auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void>;
+
+/**
+ * @param key_namespace
+ * @return A result containing a boolean indicating whether `key_namespace` is auto-generated or
+ * user-generated on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::UnsupportedNamespace if the namespace is not supported.
+ */
+[[nodiscard]] auto is_auto_generated(std::string_view key_namespace)
+        -> outcome_v2::std_result<bool>;
+
+auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>> {
+    if (nullptr == query) {
+        return ErrorCode{ErrorCodeEnum::QueryExpressionIsNull};
+    }
+
+    if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(query)) {
+        return query;
+    }
+
+    try {
+        if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        if (query = clp_s::search::ast::NarrowTypes{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        return clp_s::search::ast::ConvertToExists{}.run(query);
+    } catch (std::exception const& ex) {
+        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+    }
+}
+
+auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>> {
+    std::unordered_set<std::string_view> unique_projected_columns;
+    std::vector<std::shared_ptr<ColumnDescriptor>> projected_columns;
+    QueryHandlerImpl::ProjectionMap projected_column_to_original_key;
+
+    for (auto const& [key, types] : projections) {
+        if (unique_projected_columns.contains(key)) {
+            return ErrorCode{ErrorCodeEnum::DuplicateProjectedColumn};
+        }
+        unique_projected_columns.emplace(key);
+
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
user: login: LinZhihao-723
body: This is right, but since this is test cases so I don't think it's required...
created_at: 2025-05-02T23:57:30Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,328 @@
+#include "QueryHandlerImpl.hpp"
+
+#include <exception>
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/archive_constants.hpp"
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/ConvertToExists.hpp"
+#include "../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../clp_s/search/ast/NarrowTypes.hpp"
+#include "../../../../clp_s/search/ast/OrOfAndForm.hpp"
+#include "../../../../clp_s/search/ast/SearchUtils.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::EmptyExpr;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Pre-processes a search query by applying several transformation passes.
+ * @param query
+ * @return A result containing the transformed
+ * - ErrorCodeEnum::QueryExpressionIsNull if `query` is nullptr.
+ * - ErrorCodeEnum::QueryTransformationPassFailed if any of the transformation pass failed.
+ */
+[[nodiscard]] auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>>;
+
+/**
+ * Creates projected columns and column-to-original-key map from the given projections.
+ * @param projections
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - A vector of projected columns.
+ *   - A projected-column-to-original-key map.
+ * - The possible error codes:
+ *   - ErrorCodeEnum::DuplicateProjectedColumn if `projections` contains duplicated keys.
+ *   - ErrorCodeEnum::ColumnTokenizationFailure if failed to tokenize a projection key.
+ *   - ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure if failed to create a column
+ *     descriptor for the projected key.
+ */
+[[nodiscard]] auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>>;
+
+/**
+ * Creates initial partial resolutions for the given query and projections.
+ * @param query
+ * @param projected_column_to_original_key
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - The partial resolution for auto-generated keys.
+ *   - The partial resolution for user-generated keys.
+ * - The possible error codes:
+ *   - Forwards `is_auto_generated`'s return values.
+ *   - Forwards `initialize_partial_resolution_from_search_ast`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::create`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::next`'s return values.
+ */
+[[nodiscard]] auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>>;
+
+/**
+ * Initializes partial resolutions from a search AST.
+ * @param root The root of the search AST.
+ * @param auto_gen_namespace_partial_resolutions Returns initialized auto-gen partial resolutions.
+ * @param user_gen_namespace_partial_resolutions Returns initialized user-gen partial resolutions.
+ * @return A void result on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::AstDynamicCastFailure if failed to dynamically cast an AST node to a target
+ *   type.
+ * - Forwards `is_auto_generated`'s return values.
+ */
+[[nodiscard]] auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void>;
+
+/**
+ * @param key_namespace
+ * @return A result containing a boolean indicating whether `key_namespace` is auto-generated or
+ * user-generated on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::UnsupportedNamespace if the namespace is not supported.
+ */
+[[nodiscard]] auto is_auto_generated(std::string_view key_namespace)
+        -> outcome_v2::std_result<bool>;
+
+auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>> {
+    if (nullptr == query) {
+        return ErrorCode{ErrorCodeEnum::QueryExpressionIsNull};
+    }
+
+    if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(query)) {
+        return query;
+    }
+
+    try {
+        if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        if (query = clp_s::search::ast::NarrowTypes{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        return clp_s::search::ast::ConvertToExists{}.run(query);
+    } catch (std::exception const& ex) {
+        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+    }
+}
+
+auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>> {
+    std::unordered_set<std::string_view> unique_projected_columns;
+    std::vector<std::shared_ptr<ColumnDescriptor>> projected_columns;
+    QueryHandlerImpl::ProjectionMap projected_column_to_original_key;
+
+    for (auto const& [key, types] : projections) {
+        if (unique_projected_columns.contains(key)) {
+            return ErrorCode{ErrorCodeEnum::DuplicateProjectedColumn};
+        }
+        unique_projected_columns.emplace(key);
+
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: @LinZhihao-723 I noticed this file (`QueryHandlerImpl.cpp`) is actually in the main source directory, not in the test directory. It appears to be production code that will be used in the actual application, rather than test code.

The pre-sizing optimization would be beneficial for production code, especially when handling potentially large datasets. However, I understand if you'd prefer to keep the implementation simpler for now, particularly in a WIP PR.


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@LinZhihao-723 I noticed this file (`QueryHandlerImpl.cpp`) is actually in the main source directory, not in the test directory. It appears to be production code that will be used in the actual application, rather than test code.

The pre-sizing optimization would be beneficial for production code, especially when handling potentially large datasets. However, I understand if you'd prefer to keep the implementation simpler for now, particularly in a WIP PR.</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-02T23:57:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -10,10 +10,18 @@ namespace clp::ffi::ir_stream::search {
  * Represents all possible error codes related to KV-pair IR stream search.
  */
 enum class ErrorCodeEnum : uint8_t {
+    AstDynamicCastFailure = 1,
+    ColumnDescriptorTokenIteratorOutOfBound,
path: components/core/src/clp/ffi/ir_stream/search/ErrorCode.hpp
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Nit: Grammar in enum member name.**  
`ColumnDescriptorTokenIteratorOutOfBound` may read more naturally as `ColumnDescriptorTokenIteratorOutOfBounds`.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-03T00:03:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -10,10 +10,18 @@
  * Represents all possible error codes related to KV-pair IR stream search.
  */
 enum class ErrorCodeEnum : uint8_t {
+    AstDynamicCastFailure = 1,
+    ColumnDescriptorTokenIteratorOutOfBound,
+    ColumnTokenizationFailure,
+    DuplicateProjectedColumn,
path: components/core/src/clp/ffi/ir_stream/search/ErrorCode.hpp
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Critical: Adding new enum entries shifts existing error code values.**  
Inserting new items at the start of the enum without explicit numbering will change the integral values of the original error codes (e.g. `EncodedTextAstDecodingFailure` will no longer map to its former numeric value), breaking backward compatibility.  
Please either:  
1. Move all new entries to the end of the enum (after the existing entries).  
2. Or explicitly assign fixed values to both the new and original entries to preserve the existing numbering.  


Suggested diff to append new codes at the end:

```diff
 enum class ErrorCodeEnum : uint8_t {
-   AstDynamicCastFailure = 1,
-   ColumnDescriptorTokenIteratorOutOfBound,
-   ColumnTokenizationFailure,
-   DuplicateProjectedColumn,
    EncodedTextAstDecodingFailure,
    LiteralTypeUnexpected,
    LiteralTypeUnsupported,
    MethodNotImplemented,
+   // New error codes
+   AstDynamicCastFailure,
+   ColumnDescriptorTokenIteratorOutOfBound,
+   ColumnTokenizationFailure,
+   DuplicateProjectedColumn,
+   ProjectionColumnDescriptorCreationFailure,
+   QueryTransformationPassFailed,
+   QueryExpressionIsNull,
+   UnsupportedNamespace,
 };
```


Also applies to: 21-24

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-03T00:03:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -10,10 +10,18 @@
  * Represents all possible error codes related to KV-pair IR stream search.
  */
 enum class ErrorCodeEnum : uint8_t {
+    AstDynamicCastFailure = 1,
+    ColumnDescriptorTokenIteratorOutOfBound,
+    ColumnTokenizationFailure,
+    DuplicateProjectedColumn,
path: components/core/src/clp/ffi/ir_stream/search/ErrorCode.hpp
user: login: LinZhihao-723
body: The backward compatibility shouldn't be a concern in this use case.
created_at: 2025-05-03T16:51:25Z
author_association: MEMBER
diff_hunk: @@ -10,10 +10,18 @@
  * Represents all possible error codes related to KV-pair IR stream search.
  */
 enum class ErrorCodeEnum : uint8_t {
+    AstDynamicCastFailure = 1,
+    ColumnDescriptorTokenIteratorOutOfBound,
+    ColumnTokenizationFailure,
+    DuplicateProjectedColumn,
path: components/core/src/clp/ffi/ir_stream/search/ErrorCode.hpp
user: login: coderabbitai[bot]
body: @LinZhihao-723 Thanks for clarifying. I understand that backward compatibility isn't a concern in this specific case. I'll close this review comment.


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@LinZhihao-723 Thanks for clarifying. I understand that backward compatibility isn't a concern in this specific case. I'll close this review comment.</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-03T16:51:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,350 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param colum_descriptor
+         * @return A result containing the constructed token iterator on success, or an error code
+         * indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the column doesn't have any
+         *   token.
+         */
+        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* colum_descriptor)
+                -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            auto const token_begin_it{colum_descriptor->descriptor_begin()};
+            if (colum_descriptor->descriptor_end() == token_begin_it) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{colum_descriptor, token_begin_it};
+        }
+
+        // Methods
+        /**
+         * @return Whether this is the last token of the underlying column descriptor.
+         */
+        [[nodiscard]] auto is_last() const -> bool {
+            return m_column_descriptor->descriptor_end() == m_next_token_it;
+        }
+
+        /**
+         * @return A result containing a newly constructor iterator pointing to the next token on
+         * success, or an error code indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the current token is already
+         *   the last one.
+         */
+        [[nodiscard]] auto next() const -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            if (is_last()) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{m_column_descriptor, m_next_token_it};
+        }
+
+        [[nodiscard]] auto is_wildcard() const -> bool { return m_curr_token_it->wildcard(); }
+
+        [[nodiscard]] auto get_token() const -> std::string_view {
+            return m_curr_token_it->get_token();
+        }
+
+        [[nodiscard]] auto get_column_descriptor() const -> clp_s::search::ast::ColumnDescriptor* {
+            return m_column_descriptor;
+        }
+
+        /**
+         * @param type
+         * @return Whether the underlying column can match the given schema-tree node type.
+         */
+        [[nodiscard]] auto match(SchemaTree::Node::Type type) const -> bool {
+            return m_column_descriptor->matches_any(schema_tree_node_type_to_literal_types(type));
+        }
+
+    private:
+        // Constructor
+        ColumnDescriptorTokenIterator(
+                clp_s::search::ast::ColumnDescriptor* colum_descriptor,
+                clp_s::search::ast::DescriptorList::iterator curr_token_it
+        )
+                : m_column_descriptor{colum_descriptor},
+                  m_curr_token_it{curr_token_it},
+                  m_next_token_it{curr_token_it + 1} {}
+
+        clp_s::search::ast::ColumnDescriptor* m_column_descriptor;
+        clp_s::search::ast::DescriptorList::iterator m_curr_token_it;
+        clp_s::search::ast::DescriptorList::iterator m_next_token_it;
+    };
+
+    using ProjectionMap = std::unordered_map<clp_s::search::ast::ColumnDescriptor*, std::string>;
+
+    using PartialResolutionMap = std::
+            unordered_map<SchemaTree::Node::id_t, std::vector<ColumnDescriptorTokenIterator>>;
+
+    // Factory function
+    /**
+     * @param query The search query.
+     * @param projections The columns to project.
+     * @param case_sensitive_match Whether to use case-sensitive match for string comparison.
+     * @return A result containing the newly constructed `QueryHandler` on success, or an error code
+     * indicating the failure:
+     * - Forwards `preprocess_query`'s return values.
+     * - Forwards `create_projected_columns_and_projection_map`'s return values.
+     * - Forwards `create_initial_partial_resolutions`'s return values.
+     */
+    [[nodiscard]] static auto create(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            std::vector<std::pair<std::string, clp_s::search::ast::LiteralTypeBitmask>> const&
+                    projections,
+            bool case_sensitive_match
+    ) -> outcome_v2::std_result<QueryHandlerImpl>;
+
+    // Delete copy constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl const&) = delete;
+    auto operator=(QueryHandlerImpl const&) -> QueryHandlerImpl& = delete;
+
+    // Default move constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl&&) = default;
+    auto operator=(QueryHandlerImpl&&) -> QueryHandlerImpl& = default;
+
+    // Destructor
+    ~QueryHandlerImpl() = default;
+
+    /**
+     * Implementation of `QueryHandler::evaluate_node_id_value_pairs`.
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - TODO
+     */
+    [[nodiscard]] auto evaluate_node_id_value_pairs(
+            KeyValuePairLogEvent::NodeIdValuePairs const& auto_gen_node_id_value_pairs,
+            KeyValuePairLogEvent::NodeIdValuePairs const& user_gen_node_id_value_pairs
+    ) -> outcome_v2::std_result<AstEvaluationResult>;
+
+    /**
+     * Implementation of `QueryHandler::update_partially_resolved_columns` with new projected
+     * schema-tree node callback given as a template parameter.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @param new_projected_schema_tree_node_callback
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `handle_column_resolution_on_leaf_node`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto update_partially_resolved_columns(
+            bool is_auto_generated,
+            SchemaTree::NodeLocator const& node_locator,
+            SchemaTree::Node::id_t node_id,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    [[nodiscard]] auto get_resolved_column_to_schema_tree_node_ids() const -> std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>> {
+        return m_resolved_column_to_schema_tree_node_ids;
+    }
+
+private:
+    // Constructor
+    QueryHandlerImpl(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            PartialResolutionMap auto_gen_namespace_partial_resolutions,
+            PartialResolutionMap user_gen_namespace_partial_resolutions,
+            std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> projected_columns,
+            ProjectionMap projected_column_to_original_key,
+            bool case_sensitive_match
+    )
+            : m_query{std::move(query)},
+              m_auto_gen_namespace_partial_resolutions{
+                      std::move(auto_gen_namespace_partial_resolutions)
+              },
+              m_user_gen_namespace_partial_resolutions{
+                      std::move(user_gen_namespace_partial_resolutions)
+              },
+              m_projected_columns{std::move(projected_columns)},
+              m_projected_column_to_original_key{std::move(projected_column_to_original_key)},
+              m_case_sensitive_search{case_sensitive_match} {}
+
+    // Methods
+    /**
+     * Handles column resolution on the given schema-tree leaf node and the token in the column.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param is_auto_generated
+     * @param leaf_node_id
+     * @param leaf_node_locator
+     * @param token_it The current resolved token in the column.
+     * @param new_projected_schema_tree_node_callback
+     * @return A void result on success, or an error code indicating the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `new_projected_schema_tree_node_callback`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto handle_column_resolution_on_leaf_node(
+            bool is_auto_generated,
+            SchemaTree::Node::id_t leaf_node_id,
+            SchemaTree::NodeLocator const& leaf_node_locator,
+            ColumnDescriptorTokenIterator const& token_it,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    // Variables
+    std::shared_ptr<clp_s::search::ast::Expression> m_query;
+    PartialResolutionMap m_auto_gen_namespace_partial_resolutions;
+    PartialResolutionMap m_user_gen_namespace_partial_resolutions;
+    std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>>
+            m_resolved_column_to_schema_tree_node_ids;
+    std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> m_projected_columns;
+    ProjectionMap m_projected_column_to_original_key;
+    bool m_case_sensitive_search;
+};
+
+template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+auto QueryHandlerImpl::update_partially_resolved_columns(
+        bool is_auto_generated,
+        SchemaTree::NodeLocator const& node_locator,
+        SchemaTree::Node::id_t node_id,
+        NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+) -> outcome_v2::std_result<void> {
+    auto const parent_node_id{node_locator.get_parent_id()};
+    auto& partial_resolutions_to_update{
+            is_auto_generated ? m_auto_gen_namespace_partial_resolutions
+                              : m_user_gen_namespace_partial_resolutions
+    };
+    if (false == partial_resolutions_to_update.contains(parent_node_id)) {
+        return outcome_v2::success();
+    }
+
+    std::vector<std::pair<SchemaTree::Node::id_t, ColumnDescriptorTokenIterator>>
+            new_partial_resolutions;
+    for (auto const& token_it : partial_resolutions_to_update.at(parent_node_id)) {
+        if (SchemaTree::Node::Type::Obj == node_locator.get_type()) {
+            // Handle schema-tree non-leaf nodes.
+            if (token_it.is_last() && false == token_it.is_wildcard()) {
+                // TODO: Handle object search when supported.
+                continue;
+            }
+
+            if (token_it.is_wildcard()) {
+                new_partial_resolutions.emplace_back(node_id, token_it);
+                if (false == token_it.is_last()) {
+                    new_partial_resolutions.emplace_back(node_id, OUTCOME_TRYX(token_it.next()));
+                }
+                continue;
+            }
+
+            if (token_it.get_token() != node_locator.get_key_name()) {
+                continue;
+            }
+
+            auto const next_token_it{OUTCOME_TRYX(token_it.next())};
+            new_partial_resolutions.emplace_back(node_id, next_token_it);
+            if (false == next_token_it.is_last() && next_token_it.is_wildcard()) {
+                // Handle the case where the wildcard matches nothing
+                new_partial_resolutions.emplace_back(node_id, OUTCOME_TRYX(next_token_it.next()));
+            }
+            continue;
+        }
+
+        OUTCOME_TRYV(handle_column_resolution_on_leaf_node(
+                is_auto_generated,
+                node_id,
+                node_locator,
+                token_it,
+                new_projected_schema_tree_node_callback
+        ));
+    }
+
+    for (auto const [node_id, token_it] : new_partial_resolutions) {
+        auto [it, inserted] = partial_resolutions_to_update.try_emplace(
+                node_id,
+                std::vector<ColumnDescriptorTokenIterator>{}
+        );
+        it->second.emplace_back(token_it);
+    }
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Shadowing `node_id` hampers readability and can mislead debuggers.**

The structured binding inside the loop re-uses the name `node_id`, hiding the outer variable.  Renaming avoids accidental misuse and clarifies intent.

```diff
-    for (auto const [node_id, token_it] : new_partial_resolutions) {
+    for (auto const [child_node_id, token_it] : new_partial_resolutions) {
         auto [it, inserted] = partial_resolutions_to_update.try_emplace(
-                node_id,
+                child_node_id,
                 std::vector<ColumnDescriptorTokenIterator>{}
         );
-        it->second.emplace_back(token_it);
+        it->second.emplace_back(token_it);
     }
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    for (auto const [child_node_id, token_it] : new_partial_resolutions) {
        auto [it, inserted] = partial_resolutions_to_update.try_emplace(
                child_node_id,
                std::vector<ColumnDescriptorTokenIterator>{}
        );
        it->second.emplace_back(token_it);
    }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-03T16:56:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,350 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param colum_descriptor
+         * @return A result containing the constructed token iterator on success, or an error code
+         * indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the column doesn't have any
+         *   token.
+         */
+        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* colum_descriptor)
+                -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            auto const token_begin_it{colum_descriptor->descriptor_begin()};
+            if (colum_descriptor->descriptor_end() == token_begin_it) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{colum_descriptor, token_begin_it};
+        }
+
+        // Methods
+        /**
+         * @return Whether this is the last token of the underlying column descriptor.
+         */
+        [[nodiscard]] auto is_last() const -> bool {
+            return m_column_descriptor->descriptor_end() == m_next_token_it;
+        }
+
+        /**
+         * @return A result containing a newly constructor iterator pointing to the next token on
+         * success, or an error code indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the current token is already
+         *   the last one.
+         */
+        [[nodiscard]] auto next() const -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            if (is_last()) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{m_column_descriptor, m_next_token_it};
+        }
+
+        [[nodiscard]] auto is_wildcard() const -> bool { return m_curr_token_it->wildcard(); }
+
+        [[nodiscard]] auto get_token() const -> std::string_view {
+            return m_curr_token_it->get_token();
+        }
+
+        [[nodiscard]] auto get_column_descriptor() const -> clp_s::search::ast::ColumnDescriptor* {
+            return m_column_descriptor;
+        }
+
+        /**
+         * @param type
+         * @return Whether the underlying column can match the given schema-tree node type.
+         */
+        [[nodiscard]] auto match(SchemaTree::Node::Type type) const -> bool {
+            return m_column_descriptor->matches_any(schema_tree_node_type_to_literal_types(type));
+        }
+
+    private:
+        // Constructor
+        ColumnDescriptorTokenIterator(
+                clp_s::search::ast::ColumnDescriptor* colum_descriptor,
+                clp_s::search::ast::DescriptorList::iterator curr_token_it
+        )
+                : m_column_descriptor{colum_descriptor},
+                  m_curr_token_it{curr_token_it},
+                  m_next_token_it{curr_token_it + 1} {}
+
+        clp_s::search::ast::ColumnDescriptor* m_column_descriptor;
+        clp_s::search::ast::DescriptorList::iterator m_curr_token_it;
+        clp_s::search::ast::DescriptorList::iterator m_next_token_it;
+    };
+
+    using ProjectionMap = std::unordered_map<clp_s::search::ast::ColumnDescriptor*, std::string>;
+
+    using PartialResolutionMap = std::
+            unordered_map<SchemaTree::Node::id_t, std::vector<ColumnDescriptorTokenIterator>>;
+
+    // Factory function
+    /**
+     * @param query The search query.
+     * @param projections The columns to project.
+     * @param case_sensitive_match Whether to use case-sensitive match for string comparison.
+     * @return A result containing the newly constructed `QueryHandler` on success, or an error code
+     * indicating the failure:
+     * - Forwards `preprocess_query`'s return values.
+     * - Forwards `create_projected_columns_and_projection_map`'s return values.
+     * - Forwards `create_initial_partial_resolutions`'s return values.
+     */
+    [[nodiscard]] static auto create(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            std::vector<std::pair<std::string, clp_s::search::ast::LiteralTypeBitmask>> const&
+                    projections,
+            bool case_sensitive_match
+    ) -> outcome_v2::std_result<QueryHandlerImpl>;
+
+    // Delete copy constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl const&) = delete;
+    auto operator=(QueryHandlerImpl const&) -> QueryHandlerImpl& = delete;
+
+    // Default move constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl&&) = default;
+    auto operator=(QueryHandlerImpl&&) -> QueryHandlerImpl& = default;
+
+    // Destructor
+    ~QueryHandlerImpl() = default;
+
+    /**
+     * Implementation of `QueryHandler::evaluate_node_id_value_pairs`.
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - TODO
+     */
+    [[nodiscard]] auto evaluate_node_id_value_pairs(
+            KeyValuePairLogEvent::NodeIdValuePairs const& auto_gen_node_id_value_pairs,
+            KeyValuePairLogEvent::NodeIdValuePairs const& user_gen_node_id_value_pairs
+    ) -> outcome_v2::std_result<AstEvaluationResult>;
+
+    /**
+     * Implementation of `QueryHandler::update_partially_resolved_columns` with new projected
+     * schema-tree node callback given as a template parameter.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @param new_projected_schema_tree_node_callback
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `handle_column_resolution_on_leaf_node`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto update_partially_resolved_columns(
+            bool is_auto_generated,
+            SchemaTree::NodeLocator const& node_locator,
+            SchemaTree::Node::id_t node_id,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    [[nodiscard]] auto get_resolved_column_to_schema_tree_node_ids() const -> std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>> {
+        return m_resolved_column_to_schema_tree_node_ids;
+    }
+
+private:
+    // Constructor
+    QueryHandlerImpl(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            PartialResolutionMap auto_gen_namespace_partial_resolutions,
+            PartialResolutionMap user_gen_namespace_partial_resolutions,
+            std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> projected_columns,
+            ProjectionMap projected_column_to_original_key,
+            bool case_sensitive_match
+    )
+            : m_query{std::move(query)},
+              m_auto_gen_namespace_partial_resolutions{
+                      std::move(auto_gen_namespace_partial_resolutions)
+              },
+              m_user_gen_namespace_partial_resolutions{
+                      std::move(user_gen_namespace_partial_resolutions)
+              },
+              m_projected_columns{std::move(projected_columns)},
+              m_projected_column_to_original_key{std::move(projected_column_to_original_key)},
+              m_case_sensitive_search{case_sensitive_match} {}
+
+    // Methods
+    /**
+     * Handles column resolution on the given schema-tree leaf node and the token in the column.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param is_auto_generated
+     * @param leaf_node_id
+     * @param leaf_node_locator
+     * @param token_it The current resolved token in the column.
+     * @param new_projected_schema_tree_node_callback
+     * @return A void result on success, or an error code indicating the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `new_projected_schema_tree_node_callback`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto handle_column_resolution_on_leaf_node(
+            bool is_auto_generated,
+            SchemaTree::Node::id_t leaf_node_id,
+            SchemaTree::NodeLocator const& leaf_node_locator,
+            ColumnDescriptorTokenIterator const& token_it,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    // Variables
+    std::shared_ptr<clp_s::search::ast::Expression> m_query;
+    PartialResolutionMap m_auto_gen_namespace_partial_resolutions;
+    PartialResolutionMap m_user_gen_namespace_partial_resolutions;
+    std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>>
+            m_resolved_column_to_schema_tree_node_ids;
+    std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> m_projected_columns;
+    ProjectionMap m_projected_column_to_original_key;
+    bool m_case_sensitive_search;
+};
+
+template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+auto QueryHandlerImpl::update_partially_resolved_columns(
+        bool is_auto_generated,
+        SchemaTree::NodeLocator const& node_locator,
+        SchemaTree::Node::id_t node_id,
+        NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+) -> outcome_v2::std_result<void> {
+    auto const parent_node_id{node_locator.get_parent_id()};
+    auto& partial_resolutions_to_update{
+            is_auto_generated ? m_auto_gen_namespace_partial_resolutions
+                              : m_user_gen_namespace_partial_resolutions
+    };
+    if (false == partial_resolutions_to_update.contains(parent_node_id)) {
+        return outcome_v2::success();
+    }
+
+    std::vector<std::pair<SchemaTree::Node::id_t, ColumnDescriptorTokenIterator>>
+            new_partial_resolutions;
+    for (auto const& token_it : partial_resolutions_to_update.at(parent_node_id)) {
+        if (SchemaTree::Node::Type::Obj == node_locator.get_type()) {
+            // Handle schema-tree non-leaf nodes.
+            if (token_it.is_last() && false == token_it.is_wildcard()) {
+                // TODO: Handle object search when supported.
+                continue;
+            }
+
+            if (token_it.is_wildcard()) {
+                new_partial_resolutions.emplace_back(node_id, token_it);
+                if (false == token_it.is_last()) {
+                    new_partial_resolutions.emplace_back(node_id, OUTCOME_TRYX(token_it.next()));
+                }
+                continue;
+            }
+
+            if (token_it.get_token() != node_locator.get_key_name()) {
+                continue;
+            }
+
+            auto const next_token_it{OUTCOME_TRYX(token_it.next())};
+            new_partial_resolutions.emplace_back(node_id, next_token_it);
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

_⚠️ Potential issue_

**String comparison ignores the case-sensitivity flag.**

`token_it.get_token() != node_locator.get_key_name()` performs a straight, case-sensitive compare even when `m_case_sensitive_search` is `false`; queries that should match under case-insensitive mode will silently fail.

```diff
-            if (token_it.get_token() != node_locator.get_key_name()) {
+            auto tokens_equal = m_case_sensitive_search
+                                ? token_it.get_token() == node_locator.get_key_name()
+                                : utils::iequals(token_it.get_token(), node_locator.get_key_name());
+            if (false == tokens_equal) {
                 continue;
             }
```

(Assumes a helper `iequals` similar to the one in `utils.hpp`; adjust as needed.)




<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
            // respect case-sensitivity flag when comparing tokens
            auto tokens_equal = m_case_sensitive_search
                                ? token_it.get_token() == node_locator.get_key_name()
                                : utils::iequals(token_it.get_token(), node_locator.get_key_name());
            if (false == tokens_equal) {
                continue;
            }

            auto const next_token_it{OUTCOME_TRYX(token_it.next())};
            new_partial_resolutions.emplace_back(node_id, next_token_it);
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-03T16:56:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,350 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param colum_descriptor
+         * @return A result containing the constructed token iterator on success, or an error code
+         * indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the column doesn't have any
+         *   token.
+         */
+        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* colum_descriptor)
+                -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Typo: `colum_descriptor` → `column_descriptor`.**

The parameter and several local variables are missing the “n”, which can trip up grepping and IDE refactors.

```diff
- * @param colum_descriptor
+ * @param column_descriptor
…
-        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* colum_descriptor)
+        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* column_descriptor)
…
-            auto const token_begin_it{colum_descriptor->descriptor_begin()};
+            auto const token_begin_it{column_descriptor->descriptor_begin()};
…
-            return ColumnDescriptorTokenIterator{colum_descriptor, token_begin_it};
+            return ColumnDescriptorTokenIterator{column_descriptor, token_begin_it};
```
(Apply the same rename in the private constructor at lines 96-101.)



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
         * Creates an iterator that points to the first token in the given column descriptor.
         * @param column_descriptor
         * @return A result containing the constructed token iterator on success, or an error code
         * indicating the failure:
         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the column doesn't have any
         *   token.
         */
        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* column_descriptor)
                -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
            auto const token_begin_it{column_descriptor->descriptor_begin()};
            return ColumnDescriptorTokenIterator{column_descriptor, token_begin_it};
        }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commits 5d9b901 to 887aae8
created_at: 2025-05-03T16:56:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -25,6 +33,14 @@ auto ErrorCategory::message(ErrorCodeEnum error_enum) const -> std::string {
             return "Unsupported literal type.";
         case ErrorCodeEnum::MethodNotImplemented:
             return "The requested method is not implemented.";
+        case ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure:
+            return "Failed to create a column descriptor for the given projection.";
+        case ErrorCodeEnum::QueryExpressionIsNull:
+            return "The query expression is NULL.";
+        case ErrorCodeEnum::QueryTransformationPassFailed:
+            return "Failed to execute transformation passes on the query expression.";
+        case ErrorCodeEnum::UnsupportedNamespace:
path: components/core/src/clp/ffi/ir_stream/search/ErrorCode.cpp
user: login: gibber9809
body: Should unsupported namespace be an error? From my perspective the query is valid but just contains some columns we know won't resolve to anything.

Say `#` indicates some namespace we don't support -- a query like `message:* OR #abc:def` still makes sense to us and should match every record containing the `message` column.
created_at: 2025-05-04T00:47:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,350 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param colum_descriptor
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: gibber9809
body: ```suggestion
         * @param column_descriptor
```
created_at: 2025-05-04T00:55:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,350 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param colum_descriptor
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: gibber9809
body: Seems like rabbit also caught some other `colum_` typos in this file, so should fix those as well.
created_at: 2025-05-04T00:57:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,350 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param colum_descriptor
+         * @return A result containing the constructed token iterator on success, or an error code
+         * indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the column doesn't have any
+         *   token.
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: gibber9809
body: ```suggestion
         *   tokens.
```
created_at: 2025-05-04T00:58:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,350 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param colum_descriptor
+         * @return A result containing the constructed token iterator on success, or an error code
+         * indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the column doesn't have any
+         *   token.
+         */
+        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* colum_descriptor)
+                -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            auto const token_begin_it{colum_descriptor->descriptor_begin()};
+            if (colum_descriptor->descriptor_end() == token_begin_it) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{colum_descriptor, token_begin_it};
+        }
+
+        // Methods
+        /**
+         * @return Whether this is the last token of the underlying column descriptor.
+         */
+        [[nodiscard]] auto is_last() const -> bool {
+            return m_column_descriptor->descriptor_end() == m_next_token_it;
+        }
+
+        /**
+         * @return A result containing a newly constructor iterator pointing to the next token on
+         * success, or an error code indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the current token is already
+         *   the last one.
+         */
+        [[nodiscard]] auto next() const -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            if (is_last()) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{m_column_descriptor, m_next_token_it};
+        }
+
+        [[nodiscard]] auto is_wildcard() const -> bool { return m_curr_token_it->wildcard(); }
+
+        [[nodiscard]] auto get_token() const -> std::string_view {
+            return m_curr_token_it->get_token();
+        }
+
+        [[nodiscard]] auto get_column_descriptor() const -> clp_s::search::ast::ColumnDescriptor* {
+            return m_column_descriptor;
+        }
+
+        /**
+         * @param type
+         * @return Whether the underlying column can match the given schema-tree node type.
+         */
+        [[nodiscard]] auto match(SchemaTree::Node::Type type) const -> bool {
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: gibber9809
body: Maybe change this to `matches_type`? `match` seems a bit ambiguous on its own to me. 
created_at: 2025-05-04T01:01:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,350 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param colum_descriptor
+         * @return A result containing the constructed token iterator on success, or an error code
+         * indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the column doesn't have any
+         *   token.
+         */
+        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* colum_descriptor)
+                -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            auto const token_begin_it{colum_descriptor->descriptor_begin()};
+            if (colum_descriptor->descriptor_end() == token_begin_it) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{colum_descriptor, token_begin_it};
+        }
+
+        // Methods
+        /**
+         * @return Whether this is the last token of the underlying column descriptor.
+         */
+        [[nodiscard]] auto is_last() const -> bool {
+            return m_column_descriptor->descriptor_end() == m_next_token_it;
+        }
+
+        /**
+         * @return A result containing a newly constructor iterator pointing to the next token on
+         * success, or an error code indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the current token is already
+         *   the last one.
+         */
+        [[nodiscard]] auto next() const -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            if (is_last()) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{m_column_descriptor, m_next_token_it};
+        }
+
+        [[nodiscard]] auto is_wildcard() const -> bool { return m_curr_token_it->wildcard(); }
+
+        [[nodiscard]] auto get_token() const -> std::string_view {
+            return m_curr_token_it->get_token();
+        }
+
+        [[nodiscard]] auto get_column_descriptor() const -> clp_s::search::ast::ColumnDescriptor* {
+            return m_column_descriptor;
+        }
+
+        /**
+         * @param type
+         * @return Whether the underlying column can match the given schema-tree node type.
+         */
+        [[nodiscard]] auto match(SchemaTree::Node::Type type) const -> bool {
+            return m_column_descriptor->matches_any(schema_tree_node_type_to_literal_types(type));
+        }
+
+    private:
+        // Constructor
+        ColumnDescriptorTokenIterator(
+                clp_s::search::ast::ColumnDescriptor* colum_descriptor,
+                clp_s::search::ast::DescriptorList::iterator curr_token_it
+        )
+                : m_column_descriptor{colum_descriptor},
+                  m_curr_token_it{curr_token_it},
+                  m_next_token_it{curr_token_it + 1} {}
+
+        clp_s::search::ast::ColumnDescriptor* m_column_descriptor;
+        clp_s::search::ast::DescriptorList::iterator m_curr_token_it;
+        clp_s::search::ast::DescriptorList::iterator m_next_token_it;
+    };
+
+    using ProjectionMap = std::unordered_map<clp_s::search::ast::ColumnDescriptor*, std::string>;
+
+    using PartialResolutionMap = std::
+            unordered_map<SchemaTree::Node::id_t, std::vector<ColumnDescriptorTokenIterator>>;
+
+    // Factory function
+    /**
+     * @param query The search query.
+     * @param projections The columns to project.
+     * @param case_sensitive_match Whether to use case-sensitive match for string comparison.
+     * @return A result containing the newly constructed `QueryHandler` on success, or an error code
+     * indicating the failure:
+     * - Forwards `preprocess_query`'s return values.
+     * - Forwards `create_projected_columns_and_projection_map`'s return values.
+     * - Forwards `create_initial_partial_resolutions`'s return values.
+     */
+    [[nodiscard]] static auto create(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            std::vector<std::pair<std::string, clp_s::search::ast::LiteralTypeBitmask>> const&
+                    projections,
+            bool case_sensitive_match
+    ) -> outcome_v2::std_result<QueryHandlerImpl>;
+
+    // Delete copy constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl const&) = delete;
+    auto operator=(QueryHandlerImpl const&) -> QueryHandlerImpl& = delete;
+
+    // Default move constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl&&) = default;
+    auto operator=(QueryHandlerImpl&&) -> QueryHandlerImpl& = default;
+
+    // Destructor
+    ~QueryHandlerImpl() = default;
+
+    /**
+     * Implementation of `QueryHandler::evaluate_node_id_value_pairs`.
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - TODO
+     */
+    [[nodiscard]] auto evaluate_node_id_value_pairs(
+            KeyValuePairLogEvent::NodeIdValuePairs const& auto_gen_node_id_value_pairs,
+            KeyValuePairLogEvent::NodeIdValuePairs const& user_gen_node_id_value_pairs
+    ) -> outcome_v2::std_result<AstEvaluationResult>;
+
+    /**
+     * Implementation of `QueryHandler::update_partially_resolved_columns` with new projected
+     * schema-tree node callback given as a template parameter.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @param new_projected_schema_tree_node_callback
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `handle_column_resolution_on_leaf_node`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto update_partially_resolved_columns(
+            bool is_auto_generated,
+            SchemaTree::NodeLocator const& node_locator,
+            SchemaTree::Node::id_t node_id,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    [[nodiscard]] auto get_resolved_column_to_schema_tree_node_ids() const -> std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>> {
+        return m_resolved_column_to_schema_tree_node_ids;
+    }
+
+private:
+    // Constructor
+    QueryHandlerImpl(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            PartialResolutionMap auto_gen_namespace_partial_resolutions,
+            PartialResolutionMap user_gen_namespace_partial_resolutions,
+            std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> projected_columns,
+            ProjectionMap projected_column_to_original_key,
+            bool case_sensitive_match
+    )
+            : m_query{std::move(query)},
+              m_auto_gen_namespace_partial_resolutions{
+                      std::move(auto_gen_namespace_partial_resolutions)
+              },
+              m_user_gen_namespace_partial_resolutions{
+                      std::move(user_gen_namespace_partial_resolutions)
+              },
+              m_projected_columns{std::move(projected_columns)},
+              m_projected_column_to_original_key{std::move(projected_column_to_original_key)},
+              m_case_sensitive_search{case_sensitive_match} {}
+
+    // Methods
+    /**
+     * Handles column resolution on the given schema-tree leaf node and the token in the column.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param is_auto_generated
+     * @param leaf_node_id
+     * @param leaf_node_locator
+     * @param token_it The current resolved token in the column.
+     * @param new_projected_schema_tree_node_callback
+     * @return A void result on success, or an error code indicating the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `new_projected_schema_tree_node_callback`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto handle_column_resolution_on_leaf_node(
+            bool is_auto_generated,
+            SchemaTree::Node::id_t leaf_node_id,
+            SchemaTree::NodeLocator const& leaf_node_locator,
+            ColumnDescriptorTokenIterator const& token_it,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    // Variables
+    std::shared_ptr<clp_s::search::ast::Expression> m_query;
+    PartialResolutionMap m_auto_gen_namespace_partial_resolutions;
+    PartialResolutionMap m_user_gen_namespace_partial_resolutions;
+    std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>>
+            m_resolved_column_to_schema_tree_node_ids;
+    std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> m_projected_columns;
+    ProjectionMap m_projected_column_to_original_key;
+    bool m_case_sensitive_search;
+};
+
+template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+auto QueryHandlerImpl::update_partially_resolved_columns(
+        bool is_auto_generated,
+        SchemaTree::NodeLocator const& node_locator,
+        SchemaTree::Node::id_t node_id,
+        NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+) -> outcome_v2::std_result<void> {
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: gibber9809
body: Just curious, is there a reason to do it this way over just directly returning an error code (w/ a success code in the common case)?
created_at: 2025-05-04T01:10:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,350 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param colum_descriptor
+         * @return A result containing the constructed token iterator on success, or an error code
+         * indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the column doesn't have any
+         *   token.
+         */
+        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* colum_descriptor)
+                -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            auto const token_begin_it{colum_descriptor->descriptor_begin()};
+            if (colum_descriptor->descriptor_end() == token_begin_it) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{colum_descriptor, token_begin_it};
+        }
+
+        // Methods
+        /**
+         * @return Whether this is the last token of the underlying column descriptor.
+         */
+        [[nodiscard]] auto is_last() const -> bool {
+            return m_column_descriptor->descriptor_end() == m_next_token_it;
+        }
+
+        /**
+         * @return A result containing a newly constructor iterator pointing to the next token on
+         * success, or an error code indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the current token is already
+         *   the last one.
+         */
+        [[nodiscard]] auto next() const -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            if (is_last()) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{m_column_descriptor, m_next_token_it};
+        }
+
+        [[nodiscard]] auto is_wildcard() const -> bool { return m_curr_token_it->wildcard(); }
+
+        [[nodiscard]] auto get_token() const -> std::string_view {
+            return m_curr_token_it->get_token();
+        }
+
+        [[nodiscard]] auto get_column_descriptor() const -> clp_s::search::ast::ColumnDescriptor* {
+            return m_column_descriptor;
+        }
+
+        /**
+         * @param type
+         * @return Whether the underlying column can match the given schema-tree node type.
+         */
+        [[nodiscard]] auto match(SchemaTree::Node::Type type) const -> bool {
+            return m_column_descriptor->matches_any(schema_tree_node_type_to_literal_types(type));
+        }
+
+    private:
+        // Constructor
+        ColumnDescriptorTokenIterator(
+                clp_s::search::ast::ColumnDescriptor* colum_descriptor,
+                clp_s::search::ast::DescriptorList::iterator curr_token_it
+        )
+                : m_column_descriptor{colum_descriptor},
+                  m_curr_token_it{curr_token_it},
+                  m_next_token_it{curr_token_it + 1} {}
+
+        clp_s::search::ast::ColumnDescriptor* m_column_descriptor;
+        clp_s::search::ast::DescriptorList::iterator m_curr_token_it;
+        clp_s::search::ast::DescriptorList::iterator m_next_token_it;
+    };
+
+    using ProjectionMap = std::unordered_map<clp_s::search::ast::ColumnDescriptor*, std::string>;
+
+    using PartialResolutionMap = std::
+            unordered_map<SchemaTree::Node::id_t, std::vector<ColumnDescriptorTokenIterator>>;
+
+    // Factory function
+    /**
+     * @param query The search query.
+     * @param projections The columns to project.
+     * @param case_sensitive_match Whether to use case-sensitive match for string comparison.
+     * @return A result containing the newly constructed `QueryHandler` on success, or an error code
+     * indicating the failure:
+     * - Forwards `preprocess_query`'s return values.
+     * - Forwards `create_projected_columns_and_projection_map`'s return values.
+     * - Forwards `create_initial_partial_resolutions`'s return values.
+     */
+    [[nodiscard]] static auto create(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            std::vector<std::pair<std::string, clp_s::search::ast::LiteralTypeBitmask>> const&
+                    projections,
+            bool case_sensitive_match
+    ) -> outcome_v2::std_result<QueryHandlerImpl>;
+
+    // Delete copy constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl const&) = delete;
+    auto operator=(QueryHandlerImpl const&) -> QueryHandlerImpl& = delete;
+
+    // Default move constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl&&) = default;
+    auto operator=(QueryHandlerImpl&&) -> QueryHandlerImpl& = default;
+
+    // Destructor
+    ~QueryHandlerImpl() = default;
+
+    /**
+     * Implementation of `QueryHandler::evaluate_node_id_value_pairs`.
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - TODO
+     */
+    [[nodiscard]] auto evaluate_node_id_value_pairs(
+            KeyValuePairLogEvent::NodeIdValuePairs const& auto_gen_node_id_value_pairs,
+            KeyValuePairLogEvent::NodeIdValuePairs const& user_gen_node_id_value_pairs
+    ) -> outcome_v2::std_result<AstEvaluationResult>;
+
+    /**
+     * Implementation of `QueryHandler::update_partially_resolved_columns` with new projected
+     * schema-tree node callback given as a template parameter.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @param new_projected_schema_tree_node_callback
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `handle_column_resolution_on_leaf_node`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto update_partially_resolved_columns(
+            bool is_auto_generated,
+            SchemaTree::NodeLocator const& node_locator,
+            SchemaTree::Node::id_t node_id,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    [[nodiscard]] auto get_resolved_column_to_schema_tree_node_ids() const -> std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>> {
+        return m_resolved_column_to_schema_tree_node_ids;
+    }
+
+private:
+    // Constructor
+    QueryHandlerImpl(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            PartialResolutionMap auto_gen_namespace_partial_resolutions,
+            PartialResolutionMap user_gen_namespace_partial_resolutions,
+            std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> projected_columns,
+            ProjectionMap projected_column_to_original_key,
+            bool case_sensitive_match
+    )
+            : m_query{std::move(query)},
+              m_auto_gen_namespace_partial_resolutions{
+                      std::move(auto_gen_namespace_partial_resolutions)
+              },
+              m_user_gen_namespace_partial_resolutions{
+                      std::move(user_gen_namespace_partial_resolutions)
+              },
+              m_projected_columns{std::move(projected_columns)},
+              m_projected_column_to_original_key{std::move(projected_column_to_original_key)},
+              m_case_sensitive_search{case_sensitive_match} {}
+
+    // Methods
+    /**
+     * Handles column resolution on the given schema-tree leaf node and the token in the column.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param is_auto_generated
+     * @param leaf_node_id
+     * @param leaf_node_locator
+     * @param token_it The current resolved token in the column.
+     * @param new_projected_schema_tree_node_callback
+     * @return A void result on success, or an error code indicating the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `new_projected_schema_tree_node_callback`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto handle_column_resolution_on_leaf_node(
+            bool is_auto_generated,
+            SchemaTree::Node::id_t leaf_node_id,
+            SchemaTree::NodeLocator const& leaf_node_locator,
+            ColumnDescriptorTokenIterator const& token_it,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    // Variables
+    std::shared_ptr<clp_s::search::ast::Expression> m_query;
+    PartialResolutionMap m_auto_gen_namespace_partial_resolutions;
+    PartialResolutionMap m_user_gen_namespace_partial_resolutions;
+    std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>>
+            m_resolved_column_to_schema_tree_node_ids;
+    std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> m_projected_columns;
+    ProjectionMap m_projected_column_to_original_key;
+    bool m_case_sensitive_search;
+};
+
+template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+auto QueryHandlerImpl::update_partially_resolved_columns(
+        bool is_auto_generated,
+        SchemaTree::NodeLocator const& node_locator,
+        SchemaTree::Node::id_t node_id,
+        NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+) -> outcome_v2::std_result<void> {
+    auto const parent_node_id{node_locator.get_parent_id()};
+    auto& partial_resolutions_to_update{
+            is_auto_generated ? m_auto_gen_namespace_partial_resolutions
+                              : m_user_gen_namespace_partial_resolutions
+    };
+    if (false == partial_resolutions_to_update.contains(parent_node_id)) {
+        return outcome_v2::success();
+    }
+
+    std::vector<std::pair<SchemaTree::Node::id_t, ColumnDescriptorTokenIterator>>
+            new_partial_resolutions;
+    for (auto const& token_it : partial_resolutions_to_update.at(parent_node_id)) {
+        if (SchemaTree::Node::Type::Obj == node_locator.get_type()) {
+            // Handle schema-tree non-leaf nodes.
+            if (token_it.is_last() && false == token_it.is_wildcard()) {
+                // TODO: Handle object search when supported.
+                continue;
+            }
+
+            if (token_it.is_wildcard()) {
+                new_partial_resolutions.emplace_back(node_id, token_it);
+                if (false == token_it.is_last()) {
+                    new_partial_resolutions.emplace_back(node_id, OUTCOME_TRYX(token_it.next()));
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: gibber9809
body: Not familiar with this `OUTCOME_TRYX` macro. Will this just result in immediately forwarding the error from `token_it.next()` as the return value of `update_partially_resolved_columns`, or is the behaviour different?
created_at: 2025-05-04T01:17:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,350 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param colum_descriptor
+         * @return A result containing the constructed token iterator on success, or an error code
+         * indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the column doesn't have any
+         *   token.
+         */
+        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* colum_descriptor)
+                -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            auto const token_begin_it{colum_descriptor->descriptor_begin()};
+            if (colum_descriptor->descriptor_end() == token_begin_it) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{colum_descriptor, token_begin_it};
+        }
+
+        // Methods
+        /**
+         * @return Whether this is the last token of the underlying column descriptor.
+         */
+        [[nodiscard]] auto is_last() const -> bool {
+            return m_column_descriptor->descriptor_end() == m_next_token_it;
+        }
+
+        /**
+         * @return A result containing a newly constructor iterator pointing to the next token on
+         * success, or an error code indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the current token is already
+         *   the last one.
+         */
+        [[nodiscard]] auto next() const -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            if (is_last()) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{m_column_descriptor, m_next_token_it};
+        }
+
+        [[nodiscard]] auto is_wildcard() const -> bool { return m_curr_token_it->wildcard(); }
+
+        [[nodiscard]] auto get_token() const -> std::string_view {
+            return m_curr_token_it->get_token();
+        }
+
+        [[nodiscard]] auto get_column_descriptor() const -> clp_s::search::ast::ColumnDescriptor* {
+            return m_column_descriptor;
+        }
+
+        /**
+         * @param type
+         * @return Whether the underlying column can match the given schema-tree node type.
+         */
+        [[nodiscard]] auto match(SchemaTree::Node::Type type) const -> bool {
+            return m_column_descriptor->matches_any(schema_tree_node_type_to_literal_types(type));
+        }
+
+    private:
+        // Constructor
+        ColumnDescriptorTokenIterator(
+                clp_s::search::ast::ColumnDescriptor* colum_descriptor,
+                clp_s::search::ast::DescriptorList::iterator curr_token_it
+        )
+                : m_column_descriptor{colum_descriptor},
+                  m_curr_token_it{curr_token_it},
+                  m_next_token_it{curr_token_it + 1} {}
+
+        clp_s::search::ast::ColumnDescriptor* m_column_descriptor;
+        clp_s::search::ast::DescriptorList::iterator m_curr_token_it;
+        clp_s::search::ast::DescriptorList::iterator m_next_token_it;
+    };
+
+    using ProjectionMap = std::unordered_map<clp_s::search::ast::ColumnDescriptor*, std::string>;
+
+    using PartialResolutionMap = std::
+            unordered_map<SchemaTree::Node::id_t, std::vector<ColumnDescriptorTokenIterator>>;
+
+    // Factory function
+    /**
+     * @param query The search query.
+     * @param projections The columns to project.
+     * @param case_sensitive_match Whether to use case-sensitive match for string comparison.
+     * @return A result containing the newly constructed `QueryHandler` on success, or an error code
+     * indicating the failure:
+     * - Forwards `preprocess_query`'s return values.
+     * - Forwards `create_projected_columns_and_projection_map`'s return values.
+     * - Forwards `create_initial_partial_resolutions`'s return values.
+     */
+    [[nodiscard]] static auto create(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            std::vector<std::pair<std::string, clp_s::search::ast::LiteralTypeBitmask>> const&
+                    projections,
+            bool case_sensitive_match
+    ) -> outcome_v2::std_result<QueryHandlerImpl>;
+
+    // Delete copy constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl const&) = delete;
+    auto operator=(QueryHandlerImpl const&) -> QueryHandlerImpl& = delete;
+
+    // Default move constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl&&) = default;
+    auto operator=(QueryHandlerImpl&&) -> QueryHandlerImpl& = default;
+
+    // Destructor
+    ~QueryHandlerImpl() = default;
+
+    /**
+     * Implementation of `QueryHandler::evaluate_node_id_value_pairs`.
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - TODO
+     */
+    [[nodiscard]] auto evaluate_node_id_value_pairs(
+            KeyValuePairLogEvent::NodeIdValuePairs const& auto_gen_node_id_value_pairs,
+            KeyValuePairLogEvent::NodeIdValuePairs const& user_gen_node_id_value_pairs
+    ) -> outcome_v2::std_result<AstEvaluationResult>;
+
+    /**
+     * Implementation of `QueryHandler::update_partially_resolved_columns` with new projected
+     * schema-tree node callback given as a template parameter.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @param new_projected_schema_tree_node_callback
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `handle_column_resolution_on_leaf_node`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto update_partially_resolved_columns(
+            bool is_auto_generated,
+            SchemaTree::NodeLocator const& node_locator,
+            SchemaTree::Node::id_t node_id,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    [[nodiscard]] auto get_resolved_column_to_schema_tree_node_ids() const -> std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>> {
+        return m_resolved_column_to_schema_tree_node_ids;
+    }
+
+private:
+    // Constructor
+    QueryHandlerImpl(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            PartialResolutionMap auto_gen_namespace_partial_resolutions,
+            PartialResolutionMap user_gen_namespace_partial_resolutions,
+            std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> projected_columns,
+            ProjectionMap projected_column_to_original_key,
+            bool case_sensitive_match
+    )
+            : m_query{std::move(query)},
+              m_auto_gen_namespace_partial_resolutions{
+                      std::move(auto_gen_namespace_partial_resolutions)
+              },
+              m_user_gen_namespace_partial_resolutions{
+                      std::move(user_gen_namespace_partial_resolutions)
+              },
+              m_projected_columns{std::move(projected_columns)},
+              m_projected_column_to_original_key{std::move(projected_column_to_original_key)},
+              m_case_sensitive_search{case_sensitive_match} {}
+
+    // Methods
+    /**
+     * Handles column resolution on the given schema-tree leaf node and the token in the column.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param is_auto_generated
+     * @param leaf_node_id
+     * @param leaf_node_locator
+     * @param token_it The current resolved token in the column.
+     * @param new_projected_schema_tree_node_callback
+     * @return A void result on success, or an error code indicating the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `new_projected_schema_tree_node_callback`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto handle_column_resolution_on_leaf_node(
+            bool is_auto_generated,
+            SchemaTree::Node::id_t leaf_node_id,
+            SchemaTree::NodeLocator const& leaf_node_locator,
+            ColumnDescriptorTokenIterator const& token_it,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    // Variables
+    std::shared_ptr<clp_s::search::ast::Expression> m_query;
+    PartialResolutionMap m_auto_gen_namespace_partial_resolutions;
+    PartialResolutionMap m_user_gen_namespace_partial_resolutions;
+    std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>>
+            m_resolved_column_to_schema_tree_node_ids;
+    std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> m_projected_columns;
+    ProjectionMap m_projected_column_to_original_key;
+    bool m_case_sensitive_search;
+};
+
+template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+auto QueryHandlerImpl::update_partially_resolved_columns(
+        bool is_auto_generated,
+        SchemaTree::NodeLocator const& node_locator,
+        SchemaTree::Node::id_t node_id,
+        NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+) -> outcome_v2::std_result<void> {
+    auto const parent_node_id{node_locator.get_parent_id()};
+    auto& partial_resolutions_to_update{
+            is_auto_generated ? m_auto_gen_namespace_partial_resolutions
+                              : m_user_gen_namespace_partial_resolutions
+    };
+    if (false == partial_resolutions_to_update.contains(parent_node_id)) {
+        return outcome_v2::success();
+    }
+
+    std::vector<std::pair<SchemaTree::Node::id_t, ColumnDescriptorTokenIterator>>
+            new_partial_resolutions;
+    for (auto const& token_it : partial_resolutions_to_update.at(parent_node_id)) {
+        if (SchemaTree::Node::Type::Obj == node_locator.get_type()) {
+            // Handle schema-tree non-leaf nodes.
+            if (token_it.is_last() && false == token_it.is_wildcard()) {
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: gibber9809
body: Does this break column resolution for null columns (at least when the last token is not a wildcard)? Seems like it would because Obj can mean both object and null iirc, and we unconditionally skip leaf Obj nodes here.
created_at: 2025-05-04T01:24:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,350 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param colum_descriptor
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: LinZhihao-723
body: kk sorry mb. CLion doesn't recognize `colum` as a wrong world so I missed a lot of them... I will do a fix by searching through them all
created_at: 2025-05-04T01:37:07Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,350 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param colum_descriptor
+         * @return A result containing the constructed token iterator on success, or an error code
+         * indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the column doesn't have any
+         *   token.
+         */
+        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* colum_descriptor)
+                -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            auto const token_begin_it{colum_descriptor->descriptor_begin()};
+            if (colum_descriptor->descriptor_end() == token_begin_it) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{colum_descriptor, token_begin_it};
+        }
+
+        // Methods
+        /**
+         * @return Whether this is the last token of the underlying column descriptor.
+         */
+        [[nodiscard]] auto is_last() const -> bool {
+            return m_column_descriptor->descriptor_end() == m_next_token_it;
+        }
+
+        /**
+         * @return A result containing a newly constructor iterator pointing to the next token on
+         * success, or an error code indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the current token is already
+         *   the last one.
+         */
+        [[nodiscard]] auto next() const -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            if (is_last()) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{m_column_descriptor, m_next_token_it};
+        }
+
+        [[nodiscard]] auto is_wildcard() const -> bool { return m_curr_token_it->wildcard(); }
+
+        [[nodiscard]] auto get_token() const -> std::string_view {
+            return m_curr_token_it->get_token();
+        }
+
+        [[nodiscard]] auto get_column_descriptor() const -> clp_s::search::ast::ColumnDescriptor* {
+            return m_column_descriptor;
+        }
+
+        /**
+         * @param type
+         * @return Whether the underlying column can match the given schema-tree node type.
+         */
+        [[nodiscard]] auto match(SchemaTree::Node::Type type) const -> bool {
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: LinZhihao-723
body: Sure
created_at: 2025-05-04T01:37:33Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,350 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param colum_descriptor
+         * @return A result containing the constructed token iterator on success, or an error code
+         * indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the column doesn't have any
+         *   token.
+         */
+        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* colum_descriptor)
+                -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            auto const token_begin_it{colum_descriptor->descriptor_begin()};
+            if (colum_descriptor->descriptor_end() == token_begin_it) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{colum_descriptor, token_begin_it};
+        }
+
+        // Methods
+        /**
+         * @return Whether this is the last token of the underlying column descriptor.
+         */
+        [[nodiscard]] auto is_last() const -> bool {
+            return m_column_descriptor->descriptor_end() == m_next_token_it;
+        }
+
+        /**
+         * @return A result containing a newly constructor iterator pointing to the next token on
+         * success, or an error code indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the current token is already
+         *   the last one.
+         */
+        [[nodiscard]] auto next() const -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            if (is_last()) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{m_column_descriptor, m_next_token_it};
+        }
+
+        [[nodiscard]] auto is_wildcard() const -> bool { return m_curr_token_it->wildcard(); }
+
+        [[nodiscard]] auto get_token() const -> std::string_view {
+            return m_curr_token_it->get_token();
+        }
+
+        [[nodiscard]] auto get_column_descriptor() const -> clp_s::search::ast::ColumnDescriptor* {
+            return m_column_descriptor;
+        }
+
+        /**
+         * @param type
+         * @return Whether the underlying column can match the given schema-tree node type.
+         */
+        [[nodiscard]] auto match(SchemaTree::Node::Type type) const -> bool {
+            return m_column_descriptor->matches_any(schema_tree_node_type_to_literal_types(type));
+        }
+
+    private:
+        // Constructor
+        ColumnDescriptorTokenIterator(
+                clp_s::search::ast::ColumnDescriptor* colum_descriptor,
+                clp_s::search::ast::DescriptorList::iterator curr_token_it
+        )
+                : m_column_descriptor{colum_descriptor},
+                  m_curr_token_it{curr_token_it},
+                  m_next_token_it{curr_token_it + 1} {}
+
+        clp_s::search::ast::ColumnDescriptor* m_column_descriptor;
+        clp_s::search::ast::DescriptorList::iterator m_curr_token_it;
+        clp_s::search::ast::DescriptorList::iterator m_next_token_it;
+    };
+
+    using ProjectionMap = std::unordered_map<clp_s::search::ast::ColumnDescriptor*, std::string>;
+
+    using PartialResolutionMap = std::
+            unordered_map<SchemaTree::Node::id_t, std::vector<ColumnDescriptorTokenIterator>>;
+
+    // Factory function
+    /**
+     * @param query The search query.
+     * @param projections The columns to project.
+     * @param case_sensitive_match Whether to use case-sensitive match for string comparison.
+     * @return A result containing the newly constructed `QueryHandler` on success, or an error code
+     * indicating the failure:
+     * - Forwards `preprocess_query`'s return values.
+     * - Forwards `create_projected_columns_and_projection_map`'s return values.
+     * - Forwards `create_initial_partial_resolutions`'s return values.
+     */
+    [[nodiscard]] static auto create(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            std::vector<std::pair<std::string, clp_s::search::ast::LiteralTypeBitmask>> const&
+                    projections,
+            bool case_sensitive_match
+    ) -> outcome_v2::std_result<QueryHandlerImpl>;
+
+    // Delete copy constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl const&) = delete;
+    auto operator=(QueryHandlerImpl const&) -> QueryHandlerImpl& = delete;
+
+    // Default move constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl&&) = default;
+    auto operator=(QueryHandlerImpl&&) -> QueryHandlerImpl& = default;
+
+    // Destructor
+    ~QueryHandlerImpl() = default;
+
+    /**
+     * Implementation of `QueryHandler::evaluate_node_id_value_pairs`.
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - TODO
+     */
+    [[nodiscard]] auto evaluate_node_id_value_pairs(
+            KeyValuePairLogEvent::NodeIdValuePairs const& auto_gen_node_id_value_pairs,
+            KeyValuePairLogEvent::NodeIdValuePairs const& user_gen_node_id_value_pairs
+    ) -> outcome_v2::std_result<AstEvaluationResult>;
+
+    /**
+     * Implementation of `QueryHandler::update_partially_resolved_columns` with new projected
+     * schema-tree node callback given as a template parameter.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @param new_projected_schema_tree_node_callback
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `handle_column_resolution_on_leaf_node`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto update_partially_resolved_columns(
+            bool is_auto_generated,
+            SchemaTree::NodeLocator const& node_locator,
+            SchemaTree::Node::id_t node_id,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    [[nodiscard]] auto get_resolved_column_to_schema_tree_node_ids() const -> std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>> {
+        return m_resolved_column_to_schema_tree_node_ids;
+    }
+
+private:
+    // Constructor
+    QueryHandlerImpl(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            PartialResolutionMap auto_gen_namespace_partial_resolutions,
+            PartialResolutionMap user_gen_namespace_partial_resolutions,
+            std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> projected_columns,
+            ProjectionMap projected_column_to_original_key,
+            bool case_sensitive_match
+    )
+            : m_query{std::move(query)},
+              m_auto_gen_namespace_partial_resolutions{
+                      std::move(auto_gen_namespace_partial_resolutions)
+              },
+              m_user_gen_namespace_partial_resolutions{
+                      std::move(user_gen_namespace_partial_resolutions)
+              },
+              m_projected_columns{std::move(projected_columns)},
+              m_projected_column_to_original_key{std::move(projected_column_to_original_key)},
+              m_case_sensitive_search{case_sensitive_match} {}
+
+    // Methods
+    /**
+     * Handles column resolution on the given schema-tree leaf node and the token in the column.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param is_auto_generated
+     * @param leaf_node_id
+     * @param leaf_node_locator
+     * @param token_it The current resolved token in the column.
+     * @param new_projected_schema_tree_node_callback
+     * @return A void result on success, or an error code indicating the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `new_projected_schema_tree_node_callback`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto handle_column_resolution_on_leaf_node(
+            bool is_auto_generated,
+            SchemaTree::Node::id_t leaf_node_id,
+            SchemaTree::NodeLocator const& leaf_node_locator,
+            ColumnDescriptorTokenIterator const& token_it,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    // Variables
+    std::shared_ptr<clp_s::search::ast::Expression> m_query;
+    PartialResolutionMap m_auto_gen_namespace_partial_resolutions;
+    PartialResolutionMap m_user_gen_namespace_partial_resolutions;
+    std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>>
+            m_resolved_column_to_schema_tree_node_ids;
+    std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> m_projected_columns;
+    ProjectionMap m_projected_column_to_original_key;
+    bool m_case_sensitive_search;
+};
+
+template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+auto QueryHandlerImpl::update_partially_resolved_columns(
+        bool is_auto_generated,
+        SchemaTree::NodeLocator const& node_locator,
+        SchemaTree::Node::id_t node_id,
+        NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+) -> outcome_v2::std_result<void> {
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: LinZhihao-723
body: This is a Rust-style error handling (`std::Result<()>`):
-  A result only returns an error code when the error happens. That means there should be no "Success" in the error code. The caller must always unwrap the result to see if an error happened.
- This makes it easier to unwrap and forward the error using `TRY` macro (in Rust, `?` operator). I have several uses of `OUTCOME_TRYV` which checks and forwards the error with a single line.

created_at: 2025-05-04T01:41:35Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,350 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param colum_descriptor
+         * @return A result containing the constructed token iterator on success, or an error code
+         * indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the column doesn't have any
+         *   token.
+         */
+        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* colum_descriptor)
+                -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            auto const token_begin_it{colum_descriptor->descriptor_begin()};
+            if (colum_descriptor->descriptor_end() == token_begin_it) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{colum_descriptor, token_begin_it};
+        }
+
+        // Methods
+        /**
+         * @return Whether this is the last token of the underlying column descriptor.
+         */
+        [[nodiscard]] auto is_last() const -> bool {
+            return m_column_descriptor->descriptor_end() == m_next_token_it;
+        }
+
+        /**
+         * @return A result containing a newly constructor iterator pointing to the next token on
+         * success, or an error code indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the current token is already
+         *   the last one.
+         */
+        [[nodiscard]] auto next() const -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            if (is_last()) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{m_column_descriptor, m_next_token_it};
+        }
+
+        [[nodiscard]] auto is_wildcard() const -> bool { return m_curr_token_it->wildcard(); }
+
+        [[nodiscard]] auto get_token() const -> std::string_view {
+            return m_curr_token_it->get_token();
+        }
+
+        [[nodiscard]] auto get_column_descriptor() const -> clp_s::search::ast::ColumnDescriptor* {
+            return m_column_descriptor;
+        }
+
+        /**
+         * @param type
+         * @return Whether the underlying column can match the given schema-tree node type.
+         */
+        [[nodiscard]] auto match(SchemaTree::Node::Type type) const -> bool {
+            return m_column_descriptor->matches_any(schema_tree_node_type_to_literal_types(type));
+        }
+
+    private:
+        // Constructor
+        ColumnDescriptorTokenIterator(
+                clp_s::search::ast::ColumnDescriptor* colum_descriptor,
+                clp_s::search::ast::DescriptorList::iterator curr_token_it
+        )
+                : m_column_descriptor{colum_descriptor},
+                  m_curr_token_it{curr_token_it},
+                  m_next_token_it{curr_token_it + 1} {}
+
+        clp_s::search::ast::ColumnDescriptor* m_column_descriptor;
+        clp_s::search::ast::DescriptorList::iterator m_curr_token_it;
+        clp_s::search::ast::DescriptorList::iterator m_next_token_it;
+    };
+
+    using ProjectionMap = std::unordered_map<clp_s::search::ast::ColumnDescriptor*, std::string>;
+
+    using PartialResolutionMap = std::
+            unordered_map<SchemaTree::Node::id_t, std::vector<ColumnDescriptorTokenIterator>>;
+
+    // Factory function
+    /**
+     * @param query The search query.
+     * @param projections The columns to project.
+     * @param case_sensitive_match Whether to use case-sensitive match for string comparison.
+     * @return A result containing the newly constructed `QueryHandler` on success, or an error code
+     * indicating the failure:
+     * - Forwards `preprocess_query`'s return values.
+     * - Forwards `create_projected_columns_and_projection_map`'s return values.
+     * - Forwards `create_initial_partial_resolutions`'s return values.
+     */
+    [[nodiscard]] static auto create(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            std::vector<std::pair<std::string, clp_s::search::ast::LiteralTypeBitmask>> const&
+                    projections,
+            bool case_sensitive_match
+    ) -> outcome_v2::std_result<QueryHandlerImpl>;
+
+    // Delete copy constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl const&) = delete;
+    auto operator=(QueryHandlerImpl const&) -> QueryHandlerImpl& = delete;
+
+    // Default move constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl&&) = default;
+    auto operator=(QueryHandlerImpl&&) -> QueryHandlerImpl& = default;
+
+    // Destructor
+    ~QueryHandlerImpl() = default;
+
+    /**
+     * Implementation of `QueryHandler::evaluate_node_id_value_pairs`.
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - TODO
+     */
+    [[nodiscard]] auto evaluate_node_id_value_pairs(
+            KeyValuePairLogEvent::NodeIdValuePairs const& auto_gen_node_id_value_pairs,
+            KeyValuePairLogEvent::NodeIdValuePairs const& user_gen_node_id_value_pairs
+    ) -> outcome_v2::std_result<AstEvaluationResult>;
+
+    /**
+     * Implementation of `QueryHandler::update_partially_resolved_columns` with new projected
+     * schema-tree node callback given as a template parameter.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @param new_projected_schema_tree_node_callback
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `handle_column_resolution_on_leaf_node`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto update_partially_resolved_columns(
+            bool is_auto_generated,
+            SchemaTree::NodeLocator const& node_locator,
+            SchemaTree::Node::id_t node_id,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    [[nodiscard]] auto get_resolved_column_to_schema_tree_node_ids() const -> std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>> {
+        return m_resolved_column_to_schema_tree_node_ids;
+    }
+
+private:
+    // Constructor
+    QueryHandlerImpl(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            PartialResolutionMap auto_gen_namespace_partial_resolutions,
+            PartialResolutionMap user_gen_namespace_partial_resolutions,
+            std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> projected_columns,
+            ProjectionMap projected_column_to_original_key,
+            bool case_sensitive_match
+    )
+            : m_query{std::move(query)},
+              m_auto_gen_namespace_partial_resolutions{
+                      std::move(auto_gen_namespace_partial_resolutions)
+              },
+              m_user_gen_namespace_partial_resolutions{
+                      std::move(user_gen_namespace_partial_resolutions)
+              },
+              m_projected_columns{std::move(projected_columns)},
+              m_projected_column_to_original_key{std::move(projected_column_to_original_key)},
+              m_case_sensitive_search{case_sensitive_match} {}
+
+    // Methods
+    /**
+     * Handles column resolution on the given schema-tree leaf node and the token in the column.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param is_auto_generated
+     * @param leaf_node_id
+     * @param leaf_node_locator
+     * @param token_it The current resolved token in the column.
+     * @param new_projected_schema_tree_node_callback
+     * @return A void result on success, or an error code indicating the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `new_projected_schema_tree_node_callback`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto handle_column_resolution_on_leaf_node(
+            bool is_auto_generated,
+            SchemaTree::Node::id_t leaf_node_id,
+            SchemaTree::NodeLocator const& leaf_node_locator,
+            ColumnDescriptorTokenIterator const& token_it,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    // Variables
+    std::shared_ptr<clp_s::search::ast::Expression> m_query;
+    PartialResolutionMap m_auto_gen_namespace_partial_resolutions;
+    PartialResolutionMap m_user_gen_namespace_partial_resolutions;
+    std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>>
+            m_resolved_column_to_schema_tree_node_ids;
+    std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> m_projected_columns;
+    ProjectionMap m_projected_column_to_original_key;
+    bool m_case_sensitive_search;
+};
+
+template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+auto QueryHandlerImpl::update_partially_resolved_columns(
+        bool is_auto_generated,
+        SchemaTree::NodeLocator const& node_locator,
+        SchemaTree::Node::id_t node_id,
+        NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+) -> outcome_v2::std_result<void> {
+    auto const parent_node_id{node_locator.get_parent_id()};
+    auto& partial_resolutions_to_update{
+            is_auto_generated ? m_auto_gen_namespace_partial_resolutions
+                              : m_user_gen_namespace_partial_resolutions
+    };
+    if (false == partial_resolutions_to_update.contains(parent_node_id)) {
+        return outcome_v2::success();
+    }
+
+    std::vector<std::pair<SchemaTree::Node::id_t, ColumnDescriptorTokenIterator>>
+            new_partial_resolutions;
+    for (auto const& token_it : partial_resolutions_to_update.at(parent_node_id)) {
+        if (SchemaTree::Node::Type::Obj == node_locator.get_type()) {
+            // Handle schema-tree non-leaf nodes.
+            if (token_it.is_last() && false == token_it.is_wildcard()) {
+                // TODO: Handle object search when supported.
+                continue;
+            }
+
+            if (token_it.is_wildcard()) {
+                new_partial_resolutions.emplace_back(node_id, token_it);
+                if (false == token_it.is_last()) {
+                    new_partial_resolutions.emplace_back(node_id, OUTCOME_TRYX(token_it.next()));
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: LinZhihao-723
body: Yep. As explained above, this is like Rust's `try` operator:
- If an error happens, forward it to the caller
- Otherwise, unwrap it to the value.
created_at: 2025-05-04T01:43:17Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,350 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param colum_descriptor
+         * @return A result containing the constructed token iterator on success, or an error code
+         * indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the column doesn't have any
+         *   token.
+         */
+        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* colum_descriptor)
+                -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            auto const token_begin_it{colum_descriptor->descriptor_begin()};
+            if (colum_descriptor->descriptor_end() == token_begin_it) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{colum_descriptor, token_begin_it};
+        }
+
+        // Methods
+        /**
+         * @return Whether this is the last token of the underlying column descriptor.
+         */
+        [[nodiscard]] auto is_last() const -> bool {
+            return m_column_descriptor->descriptor_end() == m_next_token_it;
+        }
+
+        /**
+         * @return A result containing a newly constructor iterator pointing to the next token on
+         * success, or an error code indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the current token is already
+         *   the last one.
+         */
+        [[nodiscard]] auto next() const -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            if (is_last()) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{m_column_descriptor, m_next_token_it};
+        }
+
+        [[nodiscard]] auto is_wildcard() const -> bool { return m_curr_token_it->wildcard(); }
+
+        [[nodiscard]] auto get_token() const -> std::string_view {
+            return m_curr_token_it->get_token();
+        }
+
+        [[nodiscard]] auto get_column_descriptor() const -> clp_s::search::ast::ColumnDescriptor* {
+            return m_column_descriptor;
+        }
+
+        /**
+         * @param type
+         * @return Whether the underlying column can match the given schema-tree node type.
+         */
+        [[nodiscard]] auto match(SchemaTree::Node::Type type) const -> bool {
+            return m_column_descriptor->matches_any(schema_tree_node_type_to_literal_types(type));
+        }
+
+    private:
+        // Constructor
+        ColumnDescriptorTokenIterator(
+                clp_s::search::ast::ColumnDescriptor* colum_descriptor,
+                clp_s::search::ast::DescriptorList::iterator curr_token_it
+        )
+                : m_column_descriptor{colum_descriptor},
+                  m_curr_token_it{curr_token_it},
+                  m_next_token_it{curr_token_it + 1} {}
+
+        clp_s::search::ast::ColumnDescriptor* m_column_descriptor;
+        clp_s::search::ast::DescriptorList::iterator m_curr_token_it;
+        clp_s::search::ast::DescriptorList::iterator m_next_token_it;
+    };
+
+    using ProjectionMap = std::unordered_map<clp_s::search::ast::ColumnDescriptor*, std::string>;
+
+    using PartialResolutionMap = std::
+            unordered_map<SchemaTree::Node::id_t, std::vector<ColumnDescriptorTokenIterator>>;
+
+    // Factory function
+    /**
+     * @param query The search query.
+     * @param projections The columns to project.
+     * @param case_sensitive_match Whether to use case-sensitive match for string comparison.
+     * @return A result containing the newly constructed `QueryHandler` on success, or an error code
+     * indicating the failure:
+     * - Forwards `preprocess_query`'s return values.
+     * - Forwards `create_projected_columns_and_projection_map`'s return values.
+     * - Forwards `create_initial_partial_resolutions`'s return values.
+     */
+    [[nodiscard]] static auto create(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            std::vector<std::pair<std::string, clp_s::search::ast::LiteralTypeBitmask>> const&
+                    projections,
+            bool case_sensitive_match
+    ) -> outcome_v2::std_result<QueryHandlerImpl>;
+
+    // Delete copy constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl const&) = delete;
+    auto operator=(QueryHandlerImpl const&) -> QueryHandlerImpl& = delete;
+
+    // Default move constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl&&) = default;
+    auto operator=(QueryHandlerImpl&&) -> QueryHandlerImpl& = default;
+
+    // Destructor
+    ~QueryHandlerImpl() = default;
+
+    /**
+     * Implementation of `QueryHandler::evaluate_node_id_value_pairs`.
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - TODO
+     */
+    [[nodiscard]] auto evaluate_node_id_value_pairs(
+            KeyValuePairLogEvent::NodeIdValuePairs const& auto_gen_node_id_value_pairs,
+            KeyValuePairLogEvent::NodeIdValuePairs const& user_gen_node_id_value_pairs
+    ) -> outcome_v2::std_result<AstEvaluationResult>;
+
+    /**
+     * Implementation of `QueryHandler::update_partially_resolved_columns` with new projected
+     * schema-tree node callback given as a template parameter.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @param new_projected_schema_tree_node_callback
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `handle_column_resolution_on_leaf_node`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto update_partially_resolved_columns(
+            bool is_auto_generated,
+            SchemaTree::NodeLocator const& node_locator,
+            SchemaTree::Node::id_t node_id,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    [[nodiscard]] auto get_resolved_column_to_schema_tree_node_ids() const -> std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>> {
+        return m_resolved_column_to_schema_tree_node_ids;
+    }
+
+private:
+    // Constructor
+    QueryHandlerImpl(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            PartialResolutionMap auto_gen_namespace_partial_resolutions,
+            PartialResolutionMap user_gen_namespace_partial_resolutions,
+            std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> projected_columns,
+            ProjectionMap projected_column_to_original_key,
+            bool case_sensitive_match
+    )
+            : m_query{std::move(query)},
+              m_auto_gen_namespace_partial_resolutions{
+                      std::move(auto_gen_namespace_partial_resolutions)
+              },
+              m_user_gen_namespace_partial_resolutions{
+                      std::move(user_gen_namespace_partial_resolutions)
+              },
+              m_projected_columns{std::move(projected_columns)},
+              m_projected_column_to_original_key{std::move(projected_column_to_original_key)},
+              m_case_sensitive_search{case_sensitive_match} {}
+
+    // Methods
+    /**
+     * Handles column resolution on the given schema-tree leaf node and the token in the column.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param is_auto_generated
+     * @param leaf_node_id
+     * @param leaf_node_locator
+     * @param token_it The current resolved token in the column.
+     * @param new_projected_schema_tree_node_callback
+     * @return A void result on success, or an error code indicating the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `new_projected_schema_tree_node_callback`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto handle_column_resolution_on_leaf_node(
+            bool is_auto_generated,
+            SchemaTree::Node::id_t leaf_node_id,
+            SchemaTree::NodeLocator const& leaf_node_locator,
+            ColumnDescriptorTokenIterator const& token_it,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    // Variables
+    std::shared_ptr<clp_s::search::ast::Expression> m_query;
+    PartialResolutionMap m_auto_gen_namespace_partial_resolutions;
+    PartialResolutionMap m_user_gen_namespace_partial_resolutions;
+    std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>>
+            m_resolved_column_to_schema_tree_node_ids;
+    std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> m_projected_columns;
+    ProjectionMap m_projected_column_to_original_key;
+    bool m_case_sensitive_search;
+};
+
+template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+auto QueryHandlerImpl::update_partially_resolved_columns(
+        bool is_auto_generated,
+        SchemaTree::NodeLocator const& node_locator,
+        SchemaTree::Node::id_t node_id,
+        NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+) -> outcome_v2::std_result<void> {
+    auto const parent_node_id{node_locator.get_parent_id()};
+    auto& partial_resolutions_to_update{
+            is_auto_generated ? m_auto_gen_namespace_partial_resolutions
+                              : m_user_gen_namespace_partial_resolutions
+    };
+    if (false == partial_resolutions_to_update.contains(parent_node_id)) {
+        return outcome_v2::success();
+    }
+
+    std::vector<std::pair<SchemaTree::Node::id_t, ColumnDescriptorTokenIterator>>
+            new_partial_resolutions;
+    for (auto const& token_it : partial_resolutions_to_update.at(parent_node_id)) {
+        if (SchemaTree::Node::Type::Obj == node_locator.get_type()) {
+            // Handle schema-tree non-leaf nodes.
+            if (token_it.is_last() && false == token_it.is_wildcard()) {
+                // TODO: Handle object search when supported.
+                continue;
+            }
+
+            if (token_it.is_wildcard()) {
+                new_partial_resolutions.emplace_back(node_id, token_it);
+                if (false == token_it.is_last()) {
+                    new_partial_resolutions.emplace_back(node_id, OUTCOME_TRYX(token_it.next()));
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: LinZhihao-723
body: fyi, this is the reference implementation:
```
/**
 * This macro works the same way as Rust's `?` operator for error propagation.
 */
#define CLP_ERROR_HANDLING_TRY(expr) \
    ({ \
        auto result{(expr)}; \
        if (result.has_error()) { \
            return result.error(); \
        } \
        using ReturnType = decltype(result.value()); \
        static_assert(std::is_move_constructible_v<ReturnType>); \
        std::move(result.value()); \
    })
```
Basically, it's using gcc/clang's extension support which turns this long statement's value into the value of the last expression.
created_at: 2025-05-04T01:44:31Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,350 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param colum_descriptor
+         * @return A result containing the constructed token iterator on success, or an error code
+         * indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the column doesn't have any
+         *   token.
+         */
+        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* colum_descriptor)
+                -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            auto const token_begin_it{colum_descriptor->descriptor_begin()};
+            if (colum_descriptor->descriptor_end() == token_begin_it) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{colum_descriptor, token_begin_it};
+        }
+
+        // Methods
+        /**
+         * @return Whether this is the last token of the underlying column descriptor.
+         */
+        [[nodiscard]] auto is_last() const -> bool {
+            return m_column_descriptor->descriptor_end() == m_next_token_it;
+        }
+
+        /**
+         * @return A result containing a newly constructor iterator pointing to the next token on
+         * success, or an error code indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the current token is already
+         *   the last one.
+         */
+        [[nodiscard]] auto next() const -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            if (is_last()) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{m_column_descriptor, m_next_token_it};
+        }
+
+        [[nodiscard]] auto is_wildcard() const -> bool { return m_curr_token_it->wildcard(); }
+
+        [[nodiscard]] auto get_token() const -> std::string_view {
+            return m_curr_token_it->get_token();
+        }
+
+        [[nodiscard]] auto get_column_descriptor() const -> clp_s::search::ast::ColumnDescriptor* {
+            return m_column_descriptor;
+        }
+
+        /**
+         * @param type
+         * @return Whether the underlying column can match the given schema-tree node type.
+         */
+        [[nodiscard]] auto match(SchemaTree::Node::Type type) const -> bool {
+            return m_column_descriptor->matches_any(schema_tree_node_type_to_literal_types(type));
+        }
+
+    private:
+        // Constructor
+        ColumnDescriptorTokenIterator(
+                clp_s::search::ast::ColumnDescriptor* colum_descriptor,
+                clp_s::search::ast::DescriptorList::iterator curr_token_it
+        )
+                : m_column_descriptor{colum_descriptor},
+                  m_curr_token_it{curr_token_it},
+                  m_next_token_it{curr_token_it + 1} {}
+
+        clp_s::search::ast::ColumnDescriptor* m_column_descriptor;
+        clp_s::search::ast::DescriptorList::iterator m_curr_token_it;
+        clp_s::search::ast::DescriptorList::iterator m_next_token_it;
+    };
+
+    using ProjectionMap = std::unordered_map<clp_s::search::ast::ColumnDescriptor*, std::string>;
+
+    using PartialResolutionMap = std::
+            unordered_map<SchemaTree::Node::id_t, std::vector<ColumnDescriptorTokenIterator>>;
+
+    // Factory function
+    /**
+     * @param query The search query.
+     * @param projections The columns to project.
+     * @param case_sensitive_match Whether to use case-sensitive match for string comparison.
+     * @return A result containing the newly constructed `QueryHandler` on success, or an error code
+     * indicating the failure:
+     * - Forwards `preprocess_query`'s return values.
+     * - Forwards `create_projected_columns_and_projection_map`'s return values.
+     * - Forwards `create_initial_partial_resolutions`'s return values.
+     */
+    [[nodiscard]] static auto create(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            std::vector<std::pair<std::string, clp_s::search::ast::LiteralTypeBitmask>> const&
+                    projections,
+            bool case_sensitive_match
+    ) -> outcome_v2::std_result<QueryHandlerImpl>;
+
+    // Delete copy constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl const&) = delete;
+    auto operator=(QueryHandlerImpl const&) -> QueryHandlerImpl& = delete;
+
+    // Default move constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl&&) = default;
+    auto operator=(QueryHandlerImpl&&) -> QueryHandlerImpl& = default;
+
+    // Destructor
+    ~QueryHandlerImpl() = default;
+
+    /**
+     * Implementation of `QueryHandler::evaluate_node_id_value_pairs`.
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - TODO
+     */
+    [[nodiscard]] auto evaluate_node_id_value_pairs(
+            KeyValuePairLogEvent::NodeIdValuePairs const& auto_gen_node_id_value_pairs,
+            KeyValuePairLogEvent::NodeIdValuePairs const& user_gen_node_id_value_pairs
+    ) -> outcome_v2::std_result<AstEvaluationResult>;
+
+    /**
+     * Implementation of `QueryHandler::update_partially_resolved_columns` with new projected
+     * schema-tree node callback given as a template parameter.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @param new_projected_schema_tree_node_callback
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `handle_column_resolution_on_leaf_node`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto update_partially_resolved_columns(
+            bool is_auto_generated,
+            SchemaTree::NodeLocator const& node_locator,
+            SchemaTree::Node::id_t node_id,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    [[nodiscard]] auto get_resolved_column_to_schema_tree_node_ids() const -> std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>> {
+        return m_resolved_column_to_schema_tree_node_ids;
+    }
+
+private:
+    // Constructor
+    QueryHandlerImpl(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            PartialResolutionMap auto_gen_namespace_partial_resolutions,
+            PartialResolutionMap user_gen_namespace_partial_resolutions,
+            std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> projected_columns,
+            ProjectionMap projected_column_to_original_key,
+            bool case_sensitive_match
+    )
+            : m_query{std::move(query)},
+              m_auto_gen_namespace_partial_resolutions{
+                      std::move(auto_gen_namespace_partial_resolutions)
+              },
+              m_user_gen_namespace_partial_resolutions{
+                      std::move(user_gen_namespace_partial_resolutions)
+              },
+              m_projected_columns{std::move(projected_columns)},
+              m_projected_column_to_original_key{std::move(projected_column_to_original_key)},
+              m_case_sensitive_search{case_sensitive_match} {}
+
+    // Methods
+    /**
+     * Handles column resolution on the given schema-tree leaf node and the token in the column.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param is_auto_generated
+     * @param leaf_node_id
+     * @param leaf_node_locator
+     * @param token_it The current resolved token in the column.
+     * @param new_projected_schema_tree_node_callback
+     * @return A void result on success, or an error code indicating the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `new_projected_schema_tree_node_callback`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto handle_column_resolution_on_leaf_node(
+            bool is_auto_generated,
+            SchemaTree::Node::id_t leaf_node_id,
+            SchemaTree::NodeLocator const& leaf_node_locator,
+            ColumnDescriptorTokenIterator const& token_it,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    // Variables
+    std::shared_ptr<clp_s::search::ast::Expression> m_query;
+    PartialResolutionMap m_auto_gen_namespace_partial_resolutions;
+    PartialResolutionMap m_user_gen_namespace_partial_resolutions;
+    std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>>
+            m_resolved_column_to_schema_tree_node_ids;
+    std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> m_projected_columns;
+    ProjectionMap m_projected_column_to_original_key;
+    bool m_case_sensitive_search;
+};
+
+template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+auto QueryHandlerImpl::update_partially_resolved_columns(
+        bool is_auto_generated,
+        SchemaTree::NodeLocator const& node_locator,
+        SchemaTree::Node::id_t node_id,
+        NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+) -> outcome_v2::std_result<void> {
+    auto const parent_node_id{node_locator.get_parent_id()};
+    auto& partial_resolutions_to_update{
+            is_auto_generated ? m_auto_gen_namespace_partial_resolutions
+                              : m_user_gen_namespace_partial_resolutions
+    };
+    if (false == partial_resolutions_to_update.contains(parent_node_id)) {
+        return outcome_v2::success();
+    }
+
+    std::vector<std::pair<SchemaTree::Node::id_t, ColumnDescriptorTokenIterator>>
+            new_partial_resolutions;
+    for (auto const& token_it : partial_resolutions_to_update.at(parent_node_id)) {
+        if (SchemaTree::Node::Type::Obj == node_locator.get_type()) {
+            // Handle schema-tree non-leaf nodes.
+            if (token_it.is_last() && false == token_it.is_wildcard()) {
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: LinZhihao-723
body: hmmm true... I forgot null is also searchable. Will fix it tmr.
created_at: 2025-05-04T01:47:55Z
author_association: MEMBER
diff_hunk: @@ -25,6 +33,14 @@ auto ErrorCategory::message(ErrorCodeEnum error_enum) const -> std::string {
             return "Unsupported literal type.";
         case ErrorCodeEnum::MethodNotImplemented:
             return "The requested method is not implemented.";
+        case ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure:
+            return "Failed to create a column descriptor for the given projection.";
+        case ErrorCodeEnum::QueryExpressionIsNull:
+            return "The query expression is NULL.";
+        case ErrorCodeEnum::QueryTransformationPassFailed:
+            return "Failed to execute transformation passes on the query expression.";
+        case ErrorCodeEnum::UnsupportedNamespace:
path: components/core/src/clp/ffi/ir_stream/search/ErrorCode.cpp
user: login: LinZhihao-723
body: I'm not sure if we should just silently ignore it. Suppose an IR file is searched with a query with unknown namespace before and after it gets ingested into an clp-s archive: if we don't invalidate unknown IR namespaces, we may get different query results on the same files in two different stages. I'm not sure how big this is a problem though. @kirkrodrigues Any thoughts?
created_at: 2025-05-04T01:51:41Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,350 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param colum_descriptor
+         * @return A result containing the constructed token iterator on success, or an error code
+         * indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the column doesn't have any
+         *   token.
+         */
+        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* colum_descriptor)
+                -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            auto const token_begin_it{colum_descriptor->descriptor_begin()};
+            if (colum_descriptor->descriptor_end() == token_begin_it) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{colum_descriptor, token_begin_it};
+        }
+
+        // Methods
+        /**
+         * @return Whether this is the last token of the underlying column descriptor.
+         */
+        [[nodiscard]] auto is_last() const -> bool {
+            return m_column_descriptor->descriptor_end() == m_next_token_it;
+        }
+
+        /**
+         * @return A result containing a newly constructor iterator pointing to the next token on
+         * success, or an error code indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the current token is already
+         *   the last one.
+         */
+        [[nodiscard]] auto next() const -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            if (is_last()) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{m_column_descriptor, m_next_token_it};
+        }
+
+        [[nodiscard]] auto is_wildcard() const -> bool { return m_curr_token_it->wildcard(); }
+
+        [[nodiscard]] auto get_token() const -> std::string_view {
+            return m_curr_token_it->get_token();
+        }
+
+        [[nodiscard]] auto get_column_descriptor() const -> clp_s::search::ast::ColumnDescriptor* {
+            return m_column_descriptor;
+        }
+
+        /**
+         * @param type
+         * @return Whether the underlying column can match the given schema-tree node type.
+         */
+        [[nodiscard]] auto match(SchemaTree::Node::Type type) const -> bool {
+            return m_column_descriptor->matches_any(schema_tree_node_type_to_literal_types(type));
+        }
+
+    private:
+        // Constructor
+        ColumnDescriptorTokenIterator(
+                clp_s::search::ast::ColumnDescriptor* colum_descriptor,
+                clp_s::search::ast::DescriptorList::iterator curr_token_it
+        )
+                : m_column_descriptor{colum_descriptor},
+                  m_curr_token_it{curr_token_it},
+                  m_next_token_it{curr_token_it + 1} {}
+
+        clp_s::search::ast::ColumnDescriptor* m_column_descriptor;
+        clp_s::search::ast::DescriptorList::iterator m_curr_token_it;
+        clp_s::search::ast::DescriptorList::iterator m_next_token_it;
+    };
+
+    using ProjectionMap = std::unordered_map<clp_s::search::ast::ColumnDescriptor*, std::string>;
+
+    using PartialResolutionMap = std::
+            unordered_map<SchemaTree::Node::id_t, std::vector<ColumnDescriptorTokenIterator>>;
+
+    // Factory function
+    /**
+     * @param query The search query.
+     * @param projections The columns to project.
+     * @param case_sensitive_match Whether to use case-sensitive match for string comparison.
+     * @return A result containing the newly constructed `QueryHandler` on success, or an error code
+     * indicating the failure:
+     * - Forwards `preprocess_query`'s return values.
+     * - Forwards `create_projected_columns_and_projection_map`'s return values.
+     * - Forwards `create_initial_partial_resolutions`'s return values.
+     */
+    [[nodiscard]] static auto create(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            std::vector<std::pair<std::string, clp_s::search::ast::LiteralTypeBitmask>> const&
+                    projections,
+            bool case_sensitive_match
+    ) -> outcome_v2::std_result<QueryHandlerImpl>;
+
+    // Delete copy constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl const&) = delete;
+    auto operator=(QueryHandlerImpl const&) -> QueryHandlerImpl& = delete;
+
+    // Default move constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl&&) = default;
+    auto operator=(QueryHandlerImpl&&) -> QueryHandlerImpl& = default;
+
+    // Destructor
+    ~QueryHandlerImpl() = default;
+
+    /**
+     * Implementation of `QueryHandler::evaluate_node_id_value_pairs`.
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - TODO
+     */
+    [[nodiscard]] auto evaluate_node_id_value_pairs(
+            KeyValuePairLogEvent::NodeIdValuePairs const& auto_gen_node_id_value_pairs,
+            KeyValuePairLogEvent::NodeIdValuePairs const& user_gen_node_id_value_pairs
+    ) -> outcome_v2::std_result<AstEvaluationResult>;
+
+    /**
+     * Implementation of `QueryHandler::update_partially_resolved_columns` with new projected
+     * schema-tree node callback given as a template parameter.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @param new_projected_schema_tree_node_callback
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `handle_column_resolution_on_leaf_node`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto update_partially_resolved_columns(
+            bool is_auto_generated,
+            SchemaTree::NodeLocator const& node_locator,
+            SchemaTree::Node::id_t node_id,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    [[nodiscard]] auto get_resolved_column_to_schema_tree_node_ids() const -> std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>> {
+        return m_resolved_column_to_schema_tree_node_ids;
+    }
+
+private:
+    // Constructor
+    QueryHandlerImpl(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            PartialResolutionMap auto_gen_namespace_partial_resolutions,
+            PartialResolutionMap user_gen_namespace_partial_resolutions,
+            std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> projected_columns,
+            ProjectionMap projected_column_to_original_key,
+            bool case_sensitive_match
+    )
+            : m_query{std::move(query)},
+              m_auto_gen_namespace_partial_resolutions{
+                      std::move(auto_gen_namespace_partial_resolutions)
+              },
+              m_user_gen_namespace_partial_resolutions{
+                      std::move(user_gen_namespace_partial_resolutions)
+              },
+              m_projected_columns{std::move(projected_columns)},
+              m_projected_column_to_original_key{std::move(projected_column_to_original_key)},
+              m_case_sensitive_search{case_sensitive_match} {}
+
+    // Methods
+    /**
+     * Handles column resolution on the given schema-tree leaf node and the token in the column.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param is_auto_generated
+     * @param leaf_node_id
+     * @param leaf_node_locator
+     * @param token_it The current resolved token in the column.
+     * @param new_projected_schema_tree_node_callback
+     * @return A void result on success, or an error code indicating the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `new_projected_schema_tree_node_callback`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto handle_column_resolution_on_leaf_node(
+            bool is_auto_generated,
+            SchemaTree::Node::id_t leaf_node_id,
+            SchemaTree::NodeLocator const& leaf_node_locator,
+            ColumnDescriptorTokenIterator const& token_it,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    // Variables
+    std::shared_ptr<clp_s::search::ast::Expression> m_query;
+    PartialResolutionMap m_auto_gen_namespace_partial_resolutions;
+    PartialResolutionMap m_user_gen_namespace_partial_resolutions;
+    std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>>
+            m_resolved_column_to_schema_tree_node_ids;
+    std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> m_projected_columns;
+    ProjectionMap m_projected_column_to_original_key;
+    bool m_case_sensitive_search;
+};
+
+template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+auto QueryHandlerImpl::update_partially_resolved_columns(
+        bool is_auto_generated,
+        SchemaTree::NodeLocator const& node_locator,
+        SchemaTree::Node::id_t node_id,
+        NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+) -> outcome_v2::std_result<void> {
+    auto const parent_node_id{node_locator.get_parent_id()};
+    auto& partial_resolutions_to_update{
+            is_auto_generated ? m_auto_gen_namespace_partial_resolutions
+                              : m_user_gen_namespace_partial_resolutions
+    };
+    if (false == partial_resolutions_to_update.contains(parent_node_id)) {
+        return outcome_v2::success();
+    }
+
+    std::vector<std::pair<SchemaTree::Node::id_t, ColumnDescriptorTokenIterator>>
+            new_partial_resolutions;
+    for (auto const& token_it : partial_resolutions_to_update.at(parent_node_id)) {
+        if (SchemaTree::Node::Type::Obj == node_locator.get_type()) {
+            // Handle schema-tree non-leaf nodes.
+            if (token_it.is_last() && false == token_it.is_wildcard()) {
+                // TODO: Handle object search when supported.
+                continue;
+            }
+
+            if (token_it.is_wildcard()) {
+                new_partial_resolutions.emplace_back(node_id, token_it);
+                if (false == token_it.is_last()) {
+                    new_partial_resolutions.emplace_back(node_id, OUTCOME_TRYX(token_it.next()));
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: gibber9809
body: Makes sense, thanks 
created_at: 2025-05-04T16:35:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -25,6 +33,14 @@ auto ErrorCategory::message(ErrorCodeEnum error_enum) const -> std::string {
             return "Unsupported literal type.";
         case ErrorCodeEnum::MethodNotImplemented:
             return "The requested method is not implemented.";
+        case ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure:
+            return "Failed to create a column descriptor for the given projection.";
+        case ErrorCodeEnum::QueryExpressionIsNull:
+            return "The query expression is NULL.";
+        case ErrorCodeEnum::QueryTransformationPassFailed:
+            return "Failed to execute transformation passes on the query expression.";
+        case ErrorCodeEnum::UnsupportedNamespace:
path: components/core/src/clp/ffi/ir_stream/search/ErrorCode.cpp
user: login: kirkrodrigues
body: > we may get different query results on the same files in two different stages.

How would that happen? If the original IR file doesn't contain `#`-namespaced fields then once it's compressed into the archive, it still won't contain `#`-namespaced fields, right? If so then I would assume the query would return the same results.
created_at: 2025-05-04T17:42:17Z
author_association: MEMBER
diff_hunk: @@ -25,6 +33,14 @@ auto ErrorCategory::message(ErrorCodeEnum error_enum) const -> std::string {
             return "Unsupported literal type.";
         case ErrorCodeEnum::MethodNotImplemented:
             return "The requested method is not implemented.";
+        case ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure:
+            return "Failed to create a column descriptor for the given projection.";
+        case ErrorCodeEnum::QueryExpressionIsNull:
+            return "The query expression is NULL.";
+        case ErrorCodeEnum::QueryTransformationPassFailed:
+            return "Failed to execute transformation passes on the query expression.";
+        case ErrorCodeEnum::UnsupportedNamespace:
path: components/core/src/clp/ffi/ir_stream/search/ErrorCode.cpp
user: login: LinZhihao-723
body: What if the namespace is not supported on the IR format, but the archive will detect, for example, whether the key starts with `#` and treat it specially if true.
created_at: 2025-05-04T18:42:39Z
author_association: MEMBER
diff_hunk: @@ -25,6 +33,14 @@ auto ErrorCategory::message(ErrorCodeEnum error_enum) const -> std::string {
             return "Unsupported literal type.";
         case ErrorCodeEnum::MethodNotImplemented:
             return "The requested method is not implemented.";
+        case ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure:
+            return "Failed to create a column descriptor for the given projection.";
+        case ErrorCodeEnum::QueryExpressionIsNull:
+            return "The query expression is NULL.";
+        case ErrorCodeEnum::QueryTransformationPassFailed:
+            return "Failed to execute transformation passes on the query expression.";
+        case ErrorCodeEnum::UnsupportedNamespace:
path: components/core/src/clp/ffi/ir_stream/search/ErrorCode.cpp
user: login: kirkrodrigues
body: It sort of depends on what "unsupported" really means. I think Devin is implying that we should support trying to resolve a `#`-namespaced column even though users can't generate IR files with `#`-namespaced KV pairs. The result of this resolution would be that the column can't be resolved.

Thus, if the query is `message:* AND #abc:def`, we should return no results for the query (no matter if the IR file is outside/inside the archive).
created_at: 2025-05-04T18:49:38Z
author_association: MEMBER
diff_hunk: @@ -25,6 +33,14 @@ auto ErrorCategory::message(ErrorCodeEnum error_enum) const -> std::string {
             return "Unsupported literal type.";
         case ErrorCodeEnum::MethodNotImplemented:
             return "The requested method is not implemented.";
+        case ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure:
+            return "Failed to create a column descriptor for the given projection.";
+        case ErrorCodeEnum::QueryExpressionIsNull:
+            return "The query expression is NULL.";
+        case ErrorCodeEnum::QueryTransformationPassFailed:
+            return "Failed to execute transformation passes on the query expression.";
+        case ErrorCodeEnum::UnsupportedNamespace:
path: components/core/src/clp/ffi/ir_stream/search/ErrorCode.cpp
user: login: LinZhihao-723
body: Sure. Then I guess we should silently ignore the unknown namespaces.
created_at: 2025-05-04T19:21:53Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,350 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param colum_descriptor
+         * @return A result containing the constructed token iterator on success, or an error code
+         * indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the column doesn't have any
+         *   token.
+         */
+        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* colum_descriptor)
+                -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            auto const token_begin_it{colum_descriptor->descriptor_begin()};
+            if (colum_descriptor->descriptor_end() == token_begin_it) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{colum_descriptor, token_begin_it};
+        }
+
+        // Methods
+        /**
+         * @return Whether this is the last token of the underlying column descriptor.
+         */
+        [[nodiscard]] auto is_last() const -> bool {
+            return m_column_descriptor->descriptor_end() == m_next_token_it;
+        }
+
+        /**
+         * @return A result containing a newly constructor iterator pointing to the next token on
+         * success, or an error code indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the current token is already
+         *   the last one.
+         */
+        [[nodiscard]] auto next() const -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            if (is_last()) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{m_column_descriptor, m_next_token_it};
+        }
+
+        [[nodiscard]] auto is_wildcard() const -> bool { return m_curr_token_it->wildcard(); }
+
+        [[nodiscard]] auto get_token() const -> std::string_view {
+            return m_curr_token_it->get_token();
+        }
+
+        [[nodiscard]] auto get_column_descriptor() const -> clp_s::search::ast::ColumnDescriptor* {
+            return m_column_descriptor;
+        }
+
+        /**
+         * @param type
+         * @return Whether the underlying column can match the given schema-tree node type.
+         */
+        [[nodiscard]] auto match(SchemaTree::Node::Type type) const -> bool {
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: LinZhihao-723
body: Changed to `match_schema_tree_node_type` to be more specific.
created_at: 2025-05-04T19:28:49Z
author_association: MEMBER
diff_hunk: @@ -25,6 +33,14 @@ auto ErrorCategory::message(ErrorCodeEnum error_enum) const -> std::string {
             return "Unsupported literal type.";
         case ErrorCodeEnum::MethodNotImplemented:
             return "The requested method is not implemented.";
+        case ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure:
+            return "Failed to create a column descriptor for the given projection.";
+        case ErrorCodeEnum::QueryExpressionIsNull:
+            return "The query expression is NULL.";
+        case ErrorCodeEnum::QueryTransformationPassFailed:
+            return "Failed to execute transformation passes on the query expression.";
+        case ErrorCodeEnum::UnsupportedNamespace:
path: components/core/src/clp/ffi/ir_stream/search/ErrorCode.cpp
user: login: kirkrodrigues
body: Just to confirm, assuming the IR file doesn't contain any `#`-namespaced KV pairs but does contain a `message` KV pair:

* `message:* OR #abc:def` will return results containing the `message` field.
* `message:* AND #abc:def` will return no results.

Right?
created_at: 2025-05-04T20:11:06Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,84 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+
+#include <map>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+class ColumnQueryPossibleMatches {
+public:
+    ColumnQueryPossibleMatches(std::shared_ptr<SchemaTree> schema_tree)
+            : m_schema_tree{std::move(schema_tree)} {}
+
+    [[nodiscard]] auto get_matchable_types() const -> clp_s::search::ast::LiteralTypeBitmask {
+        return m_matchable_types;
+    }
+
+    [[nodiscard]] auto get_matchable_node_ids() const -> std::set<SchemaTree::Node::id_t> const& {
+        return m_matchable_node_ids;
+    }
+
+    [[nodiscard]] auto get_matchable_node_ids_from_literal_type(
+            clp_s::search::ast::LiteralType type
+    ) const -> std::vector<SchemaTree::Node::id_t>;
+
+    [[nodiscard]] auto get_matchable_node_ids_from_schema_tree_type(
+            SchemaTree::Node::Type type
+    ) const -> std::vector<SchemaTree::Node::id_t>;
+
+    [[nodiscard]] auto get_projectable_node_ids() const -> std::vector<SchemaTree::Node::id_t>;
+
+    auto set_matchable_node(SchemaTree::Node::id_t node_id, SchemaTree::Node::Type type) -> void {
+        m_matchable_node_ids.emplace(node_id);
+        m_matchable_types |= schema_tree_node_type_to_literal_types(type);
+    }
+
+    [[nodiscard]] auto serialize() const -> std::string;
+
+private:
+    std::shared_ptr<SchemaTree> m_schema_tree;
+    clp_s::search::ast::LiteralTypeBitmask m_matchable_types{};
+    std::set<SchemaTree::Node::id_t> m_matchable_node_ids;
+};
+
+/**
+ * Trivial implementation of `NewProjectedSchemaTreeNodeCallback` that always return success without
+ * doing anything.
+ * @param is_auto_generated
+ * @param
+ */
+[[nodiscard]] auto trivial_new_projected_schema_tree_node_callback(
+        bool is_auto_generated,
path: components/core/src/clp/ffi/ir_stream/search/test/utils.hpp
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Doxygen comment has an empty `@param` entry**

The block documents `is_auto_generated` but leaves the second `@param` blank, which clang-doc and IDEs will flag.

```diff
- * @param is_auto_generated
- * @param
+ * @param is_auto_generated   Whether the node belongs to the auto-generated namespace.
+ * @param node_id             Schema-tree node identifier receiving the projection.
+ * @param projected_key_path  Full key-path being projected (unescaped).
```

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-04T20:24:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,350 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param column_descriptor
+         * @return A result containing the constructed token iterator on success, or an error code
+         * indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds if the column doesn't have any
+         *   tokens.
+         */
+        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* column_descriptor)
+                -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            auto const token_begin_it{column_descriptor->descriptor_begin()};
+            if (column_descriptor->descriptor_end() == token_begin_it) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{column_descriptor, token_begin_it};
+        }
+
+        // Methods
+        /**
+         * @return Whether this is the last token of the underlying column descriptor.
+         */
+        [[nodiscard]] auto is_last() const -> bool {
+            return m_column_descriptor->descriptor_end() == m_next_token_it;
+        }
+
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Tiny optimisation: mark trivially safe accessors `noexcept`**

`is_last`, `is_wildcard`, `get_token`, `get_column_descriptor`, and `match_schema_tree_node_type` perform only pointer arithmetic or const look-ups.  Declaring them `noexcept` conveys intent, enables extra compiler optimisations, and documents that they will never return an error via exceptions.

_No functional change_ – optional polish.




Also applies to: 75-90

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-04T20:24:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,463 @@
+#include <map>
+#include <memory>
+#include <set>
+#include <sstream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <catch2/catch.hpp>
+#include <fmt/core.h>
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/archive_constants.hpp"
+#include "../../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../../clp_s/search/kql/kql.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../../../Value.hpp"
+#include "../QueryHandlerImpl.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::search::ast::LiteralTypeBitmask;
+
+constexpr std::string_view cRefTestStr{"*test*"};
+constexpr value_int_t cRefTestInt{0};
+constexpr value_float_t cRefTestFloat{0.0};
+constexpr value_bool_t cRefTestBool{false};
+
+/**
+ * Generates all the matchable KQL expressions based on the column queries and their matchable
+ * types. For each column query `C` matched type `T`, we generate an expression of `C: cRefTestT`,
+ * where `cRefTestT` is any of the constants defined above.
+ * @param column_namespace The namespace of the column.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - A vector of matchable KQL expressions.
+ * - A map of expected column resolutions.
+ */
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param column_namespace The namespace of the column.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - Generated projections as pairs, where each pair consists of:
+ *   - A projectable column from `column_query_to_possible_matches` that has no wildcard.
+ *   - All the matchable types of this column.
+ * - A map of expected resolved projections.
+ */
+[[nodiscard]] auto generate_projections(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+)
+        -> std::pair<
+                std::vector<std::pair<std::string, LiteralTypeBitmask>>,
+                std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param column_resolutions
+ * @return The serialized pairs.
+ */
+[[nodiscard]] auto serialize_column_node_ids_map(
+        std::map<std::string, std::set<SchemaTree::Node::id_t>> const& column_resolutions
+) -> std::string;
+
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>> {
+    std::vector<std::string> matchable_kql_expressions;
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> expected_column_resolutions;
+    for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
+        auto const column_query_with_namespace{fmt::format("{}{}", column_namespace, column_query)};
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Int
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestInt)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Float
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {:.2f}", column_query_with_namespace, cRefTestFloat)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Bool
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestBool)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Str
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestStr)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+    }
+
+    auto const single_wildcard{fmt::format("{}{}", column_namespace, "*")};
+    if (expected_column_resolutions.contains(single_wildcard)) {
+        // NOTE: The current implementation doesn't resolve single wildcard.
+        expected_column_resolutions.erase(single_wildcard);
+    }
+    return {std::move(matchable_kql_expressions), std::move(expected_column_resolutions)};
+}
+
+auto generate_projections(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+)
+        -> std::pair<
+                std::vector<std::pair<std::string, LiteralTypeBitmask>>,
+                std::map<std::string, std::set<SchemaTree::Node::id_t>>> {
+    std::vector<std::pair<std::string, LiteralTypeBitmask>> projections;
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> expected_resolved_projections;
+    for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
+        if (column_query.find('*') != std::string::npos) {
+            continue;
+        }
+        auto const projectable_node_ids{possible_matches.get_projectable_node_ids()};
+        if (projectable_node_ids.empty()) {
+            continue;
+        }
+        auto const column_query_with_namespace{fmt::format("{}{}", column_namespace, column_query)};
+        projections.emplace_back(
+                column_query_with_namespace,
+                possible_matches.get_matchable_types()
+        );
+        expected_resolved_projections.emplace(
+                column_query_with_namespace,
+                std::set<SchemaTree::Node::id_t>{
+                        projectable_node_ids.cbegin(),
+                        projectable_node_ids.cend()
+                }
+        );
+    }
+    return {std::move(projections), std::move(expected_resolved_projections)};
+}
+
+auto serialize_column_node_ids_map(
+        std::map<std::string, std::set<SchemaTree::Node::id_t>> const& column_resolutions
+) -> std::string {
+    std::string result;
+    for (auto const& [column, resolved_node_ids] : column_resolutions) {
+        result += fmt::format("{}: {}\n", column, fmt::join(resolved_node_ids, ","));
+    }
+    return result;
+}
+}  // namespace
+
+TEST_CASE(
+        "query_handler_update_partially_resolved_columns",
+        "[ffi][ir_stream][search][QueryHandler]"
+) {
+    /*
+     * <0:root:Obj>
+     *      |
+     *      |------------> <1:a:Obj>
+     *      |                  |
+     *      |--> <2:b:Int>     |--> <3:b:Obj>
+     *      |                  |        |
+     *      |--> <12:a:Int>    |        |------------> <4:c:Obj>
+     *                         |        |                  |
+     *                         |        |--> <5:d:Str>     |--> <7:a:Str>
+     *                         |        |                  |
+     *                         |        |--> <6:d:Bool>    |--> <8:d:Str>
+     *                         |        |                  |
+     *                         |        |--> <10:e:Obj>    |--> <9:d:Float>
+     *                         |                           |
+     *                         |--> <13:b:Bool>            |--> <11:f:Obj>
+     */
+    auto const schema_tree{std::make_shared<SchemaTree>()};
+    std::vector<SchemaTree::NodeLocator> const locators{
+            {SchemaTree::cRootId, "a", SchemaTree::Node::Type::Obj},
+            {SchemaTree::cRootId, "b", SchemaTree::Node::Type::Int},
+            {1, "b", SchemaTree::Node::Type::Obj},
+            {3, "c", SchemaTree::Node::Type::Obj},
+            {3, "d", SchemaTree::Node::Type::Str},
+            {3, "d", SchemaTree::Node::Type::Bool},
+            {4, "a", SchemaTree::Node::Type::Str},
+            {4, "d", SchemaTree::Node::Type::Str},
+            {4, "d", SchemaTree::Node::Type::Float},
+            {3, "e", SchemaTree::Node::Type::Obj},
+            {4, "f", SchemaTree::Node::Type::Obj},
+            {SchemaTree::cRootId, "a", SchemaTree::Node::Type::Int},
+            {1, "b", SchemaTree::Node::Type::Bool}
+    };
+    for (auto const& locator : locators) {
+        REQUIRE_NOTHROW(schema_tree->insert_node(locator));
+    }
+
+    auto const column_query_to_possible_matches{get_schema_tree_column_queries(schema_tree)};
+    CAPTURE(column_query_to_possible_matches);
+
+    auto const is_auto_generated = GENERATE(true, false);
+    auto const [matchable_kql_expressions, expected_column_resolutions]
+            = generate_matchable_kql_expressions(
+                    is_auto_generated ? clp_s::constants::cAutogenNamespace
+                                      : clp_s::constants::cDefaultNamespace,
+                    column_query_to_possible_matches
+            );
+    auto const matchable_kql_expressions_with_unrecognized_namespace{
+            generate_matchable_kql_expressions(
+                    clp_s::constants::cReservedNamespace1,
+                    column_query_to_possible_matches
+            )
+                    .first
+    };
+
+    constexpr std::string_view cUnmatchableNodeName{"unknown"};
+    for (auto const& locator : locators) {
+        REQUIRE((locator.get_key_name() != cUnmatchableNodeName));
+    }
+    std::vector<std::string> unmatchable_kql_expressions;
+    for (auto const& [matchable_column, resolved_node_ids] : expected_column_resolutions) {
+        unmatchable_kql_expressions.emplace_back(
+                fmt::format("{}.{}: {}", matchable_column, cUnmatchableNodeName, cRefTestInt)
+        );
+    }
+
+    auto const matchable_kql_query_str{
+            fmt::format("{}", fmt::join(matchable_kql_expressions, " OR "))
+    };
+    auto const unmatchable_kql_query_str{
+            fmt::format("{}", fmt::join(unmatchable_kql_expressions, " OR "))
+    };
+    auto const unrecognized_namespace_kql_query_str{fmt::format(
+            "{}",
+            fmt::join(matchable_kql_expressions_with_unrecognized_namespace, " OR ")
+    )};
+    auto const kql_query_str{fmt::format(
+            "{} OR {} OR {}",
+            matchable_kql_query_str,
+            unmatchable_kql_query_str,
+            unrecognized_namespace_kql_query_str
+    )};
+    CAPTURE(kql_query_str);
+
+    auto query_stream{std::istringstream{kql_query_str}};
+    auto query{clp_s::search::kql::parse_kql_expression(query_stream)};
+
+    auto query_handler_impl_result{QueryHandlerImpl::create(query, {}, true)};
path: components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Add a sanity check that KQL parsing succeeded**

`parse_kql_expression` can return `nullptr` on malformed input.  
Before using the resulting pointer, assert it is non-null to prevent an immediate
dereference crash and make test failures easier to diagnose.

```diff
-    auto query_stream{std::istringstream{kql_query_str}};
-    auto query{clp_s::search::kql::parse_kql_expression(query_stream)};
+    auto query_stream{std::istringstream{kql_query_str}};
+    auto query{clp_s::search::kql::parse_kql_expression(query_stream)};
+    REQUIRE(query != nullptr);  // Ensure parsing did not fail
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    auto query_stream{std::istringstream{kql_query_str}};
    auto query{clp_s::search::kql::parse_kql_expression(query_stream)};
    REQUIRE(query != nullptr);  // Ensure parsing did not fail

    auto query_handler_impl_result{QueryHandlerImpl::create(query, {}, true)};
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-04T20:24:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -25,6 +33,14 @@ auto ErrorCategory::message(ErrorCodeEnum error_enum) const -> std::string {
             return "Unsupported literal type.";
         case ErrorCodeEnum::MethodNotImplemented:
             return "The requested method is not implemented.";
+        case ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure:
+            return "Failed to create a column descriptor for the given projection.";
+        case ErrorCodeEnum::QueryExpressionIsNull:
+            return "The query expression is NULL.";
+        case ErrorCodeEnum::QueryTransformationPassFailed:
+            return "Failed to execute transformation passes on the query expression.";
+        case ErrorCodeEnum::UnsupportedNamespace:
path: components/core/src/clp/ffi/ir_stream/search/ErrorCode.cpp
user: login: LinZhihao-723
body: Yeah, `#abc:def` should be considered the same as a column that will never be resolved.
created_at: 2025-05-04T20:25:54Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,462 @@
+#include <map>
+#include <memory>
+#include <set>
+#include <sstream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <catch2/catch.hpp>
+#include <fmt/core.h>
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/archive_constants.hpp"
+#include "../../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../../clp_s/search/kql/kql.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../../../Value.hpp"
+#include "../QueryHandlerImpl.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::constants::cAutogenNamespace;
+using clp_s::constants::cDefaultNamespace;
+using clp_s::constants::cReservedNamespace1;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+constexpr std::string_view cRefTestStr{"*test*"};
+constexpr value_int_t cRefTestInt{0};
+constexpr value_float_t cRefTestFloat{0.0};
+constexpr value_bool_t cRefTestBool{false};
+
+/**
+ * Generates all the matchable KQL expressions based on the column queries and their matchable
+ * types. For each column query `C` matched type `T`, we generate an expression of `C: cRefTestT`,
+ * where `cRefTestT` is any of the constants defined above.
+ * @param column_namespace The namespace of the column.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - A vector of matchable KQL expressions.
+ * - A map of expected column resolutions.
+ */
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param column_namespace The namespace of the column.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - Generated projections as pairs, where each pair consists of:
+ *   - A projectable column from `column_query_to_possible_matches` that has no wildcard.
+ *   - All the matchable types of this column.
+ * - A map of expected resolved projections.
+ */
+[[nodiscard]] auto generate_projections(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+)
+        -> std::pair<
+                std::vector<std::pair<std::string, LiteralTypeBitmask>>,
+                std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param column_resolutions
+ * @return The serialized pairs.
+ */
+[[nodiscard]] auto serialize_column_node_ids_map(
+        std::map<std::string, std::set<SchemaTree::Node::id_t>> const& column_resolutions
+) -> std::string;
+
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>> {
+    std::vector<std::string> matchable_kql_expressions;
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> expected_column_resolutions;
+    for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
+        auto const column_query_with_namespace{fmt::format("{}{}", column_namespace, column_query)};
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Int
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestInt)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Float
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {:.2f}", column_query_with_namespace, cRefTestFloat)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Bool
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestBool)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Str
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestStr)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+    }
+
+    auto const single_wildcard{fmt::format("{}{}", column_namespace, "*")};
+    if (expected_column_resolutions.contains(single_wildcard)) {
+        // NOTE: The current implementation doesn't resolve single wildcard.
+        expected_column_resolutions.erase(single_wildcard);
+    }
+    return {std::move(matchable_kql_expressions), std::move(expected_column_resolutions)};
+}
+
+auto generate_projections(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+)
+        -> std::pair<
+                std::vector<std::pair<std::string, LiteralTypeBitmask>>,
+                std::map<std::string, std::set<SchemaTree::Node::id_t>>> {
+    std::vector<std::pair<std::string, LiteralTypeBitmask>> projections;
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> expected_resolved_projections;
+    for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
+        if (column_query.find('*') != std::string::npos) {
+            continue;
+        }
+        auto const projectable_node_ids{possible_matches.get_projectable_node_ids()};
+        if (projectable_node_ids.empty()) {
+            continue;
+        }
+        auto const column_query_with_namespace{fmt::format("{}{}", column_namespace, column_query)};
+        projections.emplace_back(
+                column_query_with_namespace,
+                possible_matches.get_matchable_types()
+        );
+        expected_resolved_projections.emplace(
+                column_query_with_namespace,
+                std::set<SchemaTree::Node::id_t>{
+                        projectable_node_ids.cbegin(),
+                        projectable_node_ids.cend()
+                }
+        );
+    }
+    return {std::move(projections), std::move(expected_resolved_projections)};
+}
+
+auto serialize_column_node_ids_map(
+        std::map<std::string, std::set<SchemaTree::Node::id_t>> const& column_resolutions
+) -> std::string {
+    std::string result;
+    for (auto const& [column, resolved_node_ids] : column_resolutions) {
+        result += fmt::format("{}: {}\n", column, fmt::join(resolved_node_ids, ","));
+    }
+    return result;
+}
+}  // namespace
+
+TEST_CASE(
+        "query_handler_update_partially_resolved_columns",
+        "[ffi][ir_stream][search][QueryHandler]"
+) {
+    /*
+     * <0:root:Obj>
+     *      |
+     *      |------------> <1:a:Obj>
+     *      |                  |
+     *      |--> <2:b:Int>     |--> <3:b:Obj>
+     *      |                  |        |
+     *      |--> <12:a:Int>    |        |------------> <4:c:Obj>
+     *                         |        |                  |
+     *                         |        |--> <5:d:Str>     |--> <7:a:Str>
+     *                         |        |                  |
+     *                         |        |--> <6:d:Bool>    |--> <8:d:Str>
+     *                         |        |                  |
+     *                         |        |--> <10:e:Obj>    |--> <9:d:Float>
+     *                         |                           |
+     *                         |--> <13:b:Bool>            |--> <11:f:Obj>
+     */
path: components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Update ASCII art diagram to match code**

The ASCII art diagram describing the schema tree structure doesn't match the nodes defined in the code. For example, nodes 12 and 13 are in the code but not clearly represented in the diagram.

Consider updating the diagram to accurately reflect all nodes including 11, 12, and 13.

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-04T20:53:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,462 @@
+#include <map>
+#include <memory>
+#include <set>
+#include <sstream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <catch2/catch.hpp>
+#include <fmt/core.h>
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/archive_constants.hpp"
+#include "../../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../../clp_s/search/kql/kql.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../../../Value.hpp"
+#include "../QueryHandlerImpl.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::constants::cAutogenNamespace;
+using clp_s::constants::cDefaultNamespace;
+using clp_s::constants::cReservedNamespace1;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+constexpr std::string_view cRefTestStr{"*test*"};
+constexpr value_int_t cRefTestInt{0};
+constexpr value_float_t cRefTestFloat{0.0};
+constexpr value_bool_t cRefTestBool{false};
+
+/**
+ * Generates all the matchable KQL expressions based on the column queries and their matchable
+ * types. For each column query `C` matched type `T`, we generate an expression of `C: cRefTestT`,
+ * where `cRefTestT` is any of the constants defined above.
+ * @param column_namespace The namespace of the column.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - A vector of matchable KQL expressions.
+ * - A map of expected column resolutions.
+ */
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param column_namespace The namespace of the column.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - Generated projections as pairs, where each pair consists of:
+ *   - A projectable column from `column_query_to_possible_matches` that has no wildcard.
+ *   - All the matchable types of this column.
+ * - A map of expected resolved projections.
+ */
+[[nodiscard]] auto generate_projections(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+)
+        -> std::pair<
+                std::vector<std::pair<std::string, LiteralTypeBitmask>>,
+                std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param column_resolutions
+ * @return The serialized pairs.
+ */
+[[nodiscard]] auto serialize_column_node_ids_map(
+        std::map<std::string, std::set<SchemaTree::Node::id_t>> const& column_resolutions
+) -> std::string;
+
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>> {
+    std::vector<std::string> matchable_kql_expressions;
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> expected_column_resolutions;
+    for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
+        auto const column_query_with_namespace{fmt::format("{}{}", column_namespace, column_query)};
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Int
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestInt)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Float
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {:.2f}", column_query_with_namespace, cRefTestFloat)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Bool
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestBool)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Str
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestStr)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+    }
+
+    auto const single_wildcard{fmt::format("{}{}", column_namespace, "*")};
+    if (expected_column_resolutions.contains(single_wildcard)) {
+        // NOTE: The current implementation doesn't resolve single wildcard.
+        expected_column_resolutions.erase(single_wildcard);
+    }
+    return {std::move(matchable_kql_expressions), std::move(expected_column_resolutions)};
+}
+
+auto generate_projections(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+)
+        -> std::pair<
+                std::vector<std::pair<std::string, LiteralTypeBitmask>>,
+                std::map<std::string, std::set<SchemaTree::Node::id_t>>> {
+    std::vector<std::pair<std::string, LiteralTypeBitmask>> projections;
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> expected_resolved_projections;
+    for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
+        if (column_query.find('*') != std::string::npos) {
+            continue;
+        }
+        auto const projectable_node_ids{possible_matches.get_projectable_node_ids()};
+        if (projectable_node_ids.empty()) {
+            continue;
+        }
+        auto const column_query_with_namespace{fmt::format("{}{}", column_namespace, column_query)};
+        projections.emplace_back(
+                column_query_with_namespace,
+                possible_matches.get_matchable_types()
+        );
+        expected_resolved_projections.emplace(
+                column_query_with_namespace,
+                std::set<SchemaTree::Node::id_t>{
+                        projectable_node_ids.cbegin(),
+                        projectable_node_ids.cend()
+                }
+        );
+    }
+    return {std::move(projections), std::move(expected_resolved_projections)};
+}
+
+auto serialize_column_node_ids_map(
+        std::map<std::string, std::set<SchemaTree::Node::id_t>> const& column_resolutions
+) -> std::string {
+    std::string result;
+    for (auto const& [column, resolved_node_ids] : column_resolutions) {
+        result += fmt::format("{}: {}\n", column, fmt::join(resolved_node_ids, ","));
+    }
+    return result;
+}
+}  // namespace
+
+TEST_CASE(
+        "query_handler_update_partially_resolved_columns",
+        "[ffi][ir_stream][search][QueryHandler]"
+) {
+    /*
+     * <0:root:Obj>
+     *      |
+     *      |------------> <1:a:Obj>
+     *      |                  |
+     *      |--> <2:b:Int>     |--> <3:b:Obj>
+     *      |                  |        |
+     *      |--> <12:a:Int>    |        |------------> <4:c:Obj>
+     *                         |        |                  |
+     *                         |        |--> <5:d:Str>     |--> <7:a:Str>
+     *                         |        |                  |
+     *                         |        |--> <6:d:Bool>    |--> <8:d:Str>
+     *                         |        |                  |
+     *                         |        |--> <10:e:Obj>    |--> <9:d:Float>
+     *                         |                           |
+     *                         |--> <13:b:Bool>            |--> <11:f:Obj>
+     */
+    auto const schema_tree{std::make_shared<SchemaTree>()};
+    std::vector<SchemaTree::NodeLocator> const locators{
+            {SchemaTree::cRootId, "a", SchemaTree::Node::Type::Obj},
+            {SchemaTree::cRootId, "b", SchemaTree::Node::Type::Int},
+            {1, "b", SchemaTree::Node::Type::Obj},
+            {3, "c", SchemaTree::Node::Type::Obj},
+            {3, "d", SchemaTree::Node::Type::Str},
+            {3, "d", SchemaTree::Node::Type::Bool},
+            {4, "a", SchemaTree::Node::Type::Str},
+            {4, "d", SchemaTree::Node::Type::Str},
+            {4, "d", SchemaTree::Node::Type::Float},
+            {3, "e", SchemaTree::Node::Type::Obj},
+            {4, "f", SchemaTree::Node::Type::Obj},
+            {SchemaTree::cRootId, "a", SchemaTree::Node::Type::Int},
+            {1, "b", SchemaTree::Node::Type::Bool}
+    };
+    for (auto const& locator : locators) {
+        REQUIRE_NOTHROW(schema_tree->insert_node(locator));
+    }
+
+    auto const column_query_to_possible_matches{get_schema_tree_column_queries(schema_tree)};
+    CAPTURE(column_query_to_possible_matches);
+
+    auto const is_auto_generated = GENERATE(true, false);
+    auto const [matchable_kql_expressions, expected_column_resolutions]
+            = generate_matchable_kql_expressions(
+                    is_auto_generated ? cAutogenNamespace : cDefaultNamespace,
+                    column_query_to_possible_matches
+            );
+    auto const matchable_kql_expressions_with_unrecognized_namespace{
+            generate_matchable_kql_expressions(
+                    cReservedNamespace1,
+                    column_query_to_possible_matches
+            )
+                    .first
+    };
+
+    constexpr std::string_view cUnmatchableNodeName{"unknown"};
+    for (auto const& locator : locators) {
+        REQUIRE((locator.get_key_name() != cUnmatchableNodeName));
+    }
+    std::vector<std::string> unmatchable_kql_expressions;
+    for (auto const& [matchable_column, resolved_node_ids] : expected_column_resolutions) {
+        unmatchable_kql_expressions.emplace_back(
+                fmt::format("{}.{}: {}", matchable_column, cUnmatchableNodeName, cRefTestInt)
+        );
+    }
+
+    auto const matchable_kql_query_str{
+            fmt::format("{}", fmt::join(matchable_kql_expressions, " OR "))
+    };
+    auto const unmatchable_kql_query_str{
+            fmt::format("{}", fmt::join(unmatchable_kql_expressions, " OR "))
+    };
+    auto const unrecognized_namespace_kql_query_str{fmt::format(
+            "{}",
+            fmt::join(matchable_kql_expressions_with_unrecognized_namespace, " OR ")
+    )};
+    auto const kql_query_str{fmt::format(
+            "{} OR {} OR {}",
+            matchable_kql_query_str,
+            unmatchable_kql_query_str,
+            unrecognized_namespace_kql_query_str
+    )};
+    CAPTURE(kql_query_str);
+
+    auto query_stream{std::istringstream{kql_query_str}};
+    auto query{clp_s::search::kql::parse_kql_expression(query_stream)};
+
+    auto query_handler_impl_result{QueryHandlerImpl::create(query, {}, true)};
+    // We disabled the check to silent clang-tidy warnings on `outcome`'s source files.
+    // Related issues: https://github.com/ned14/outcome/issues/311
+    // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)
+    REQUIRE_FALSE(query_handler_impl_result.has_error());
+    auto& query_handler_impl{query_handler_impl_result.value()};
+
+    SchemaTree::Node::id_t node_id{1};
+    for (auto const& locator : locators) {
+        REQUIRE_FALSE(query_handler_impl
+                              .update_partially_resolved_columns(
+                                      true,
+                                      locator,
+                                      node_id,
+                                      trivial_new_projected_schema_tree_node_callback
+                              )
+                              .has_error());
+        REQUIRE_FALSE(query_handler_impl
+                              .update_partially_resolved_columns(
+                                      false,
+                                      locator,
+                                      node_id,
+                                      trivial_new_projected_schema_tree_node_callback
+                              )
+                              .has_error());
+        ++node_id;
+    }
+
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> actual_column_resolutions;
+    for (auto const& [column_descriptor, resolved_nodes] :
+         query_handler_impl.get_resolved_column_to_schema_tree_node_ids())
+    {
+        REQUIRE(
+                (column_descriptor->get_namespace() == cDefaultNamespace
+                 || column_descriptor->get_namespace() == cAutogenNamespace)
+        );
+        std::vector<std::string_view> tokens;
+        for (auto const& token : column_descriptor->get_descriptor_list()) {
+            tokens.emplace_back(token.get_token());
+        }
+        auto const original_column_query{
+                fmt::format("{}{}", column_descriptor->get_namespace(), fmt::join(tokens, "."))
+        };
+        auto [it, inserted] = actual_column_resolutions.try_emplace(
+                original_column_query,
+                std::set<SchemaTree::Node::id_t>{}
+        );
+        auto& all_resolved_nodes{it->second};
+        all_resolved_nodes.insert(resolved_nodes.cbegin(), resolved_nodes.cend());
+    }
+
+    CAPTURE("\n" + serialize_column_node_ids_map(actual_column_resolutions));
+    CAPTURE("\n" + serialize_column_node_ids_map(expected_column_resolutions));
+
+    REQUIRE((actual_column_resolutions == expected_column_resolutions));
+}
path: components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Refactor duplicated schema tree setup code**

Both test cases duplicate the same schema tree setup code. This increases maintenance burden and risks inconsistencies if one setup is updated but not the other.

Extract the common setup into a helper function:

```cpp
namespace {
// Returns a schema tree with a predefined structure and the corresponding node locators
[[nodiscard]] auto create_test_schema_tree() 
    -> std::pair<std::shared_ptr<SchemaTree>, std::vector<SchemaTree::NodeLocator>> 
{
    auto schema_tree{std::make_shared<SchemaTree>()};
    std::vector<SchemaTree::NodeLocator> locators{
            {SchemaTree::cRootId, "a", SchemaTree::Node::Type::Obj},
            {SchemaTree::cRootId, "b", SchemaTree::Node::Type::Int},
            {1, "b", SchemaTree::Node::Type::Obj},
            {3, "c", SchemaTree::Node::Type::Obj},
            {3, "d", SchemaTree::Node::Type::Str},
            {3, "d", SchemaTree::Node::Type::Bool},
            {4, "a", SchemaTree::Node::Type::Str},
            {4, "d", SchemaTree::Node::Type::Str},
            {4, "d", SchemaTree::Node::Type::Float},
            {3, "e", SchemaTree::Node::Type::Obj},
            {4, "f", SchemaTree::Node::Type::Obj},
            {SchemaTree::cRootId, "a", SchemaTree::Node::Type::Int},
            {1, "b", SchemaTree::Node::Type::Bool}
    };
    for (auto const& locator : locators) {
        schema_tree->insert_node(locator);
    }
    return {schema_tree, locators};
}
}  // namespace
```

Then in each test case:
```cpp
auto [schema_tree, locators] = create_test_schema_tree();
```


Also applies to: 356-461

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Cppcheck (2.10-2)</summary>

[error] 209-209: syntax error

(syntaxError)

</details>

</details>

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-04T20:53:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,462 @@
+#include <map>
+#include <memory>
+#include <set>
+#include <sstream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <catch2/catch.hpp>
+#include <fmt/core.h>
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/archive_constants.hpp"
+#include "../../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../../clp_s/search/kql/kql.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../../../Value.hpp"
+#include "../QueryHandlerImpl.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::constants::cAutogenNamespace;
+using clp_s::constants::cDefaultNamespace;
+using clp_s::constants::cReservedNamespace1;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+constexpr std::string_view cRefTestStr{"*test*"};
+constexpr value_int_t cRefTestInt{0};
+constexpr value_float_t cRefTestFloat{0.0};
+constexpr value_bool_t cRefTestBool{false};
+
+/**
+ * Generates all the matchable KQL expressions based on the column queries and their matchable
+ * types. For each column query `C` matched type `T`, we generate an expression of `C: cRefTestT`,
+ * where `cRefTestT` is any of the constants defined above.
+ * @param column_namespace The namespace of the column.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - A vector of matchable KQL expressions.
+ * - A map of expected column resolutions.
+ */
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param column_namespace The namespace of the column.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - Generated projections as pairs, where each pair consists of:
+ *   - A projectable column from `column_query_to_possible_matches` that has no wildcard.
+ *   - All the matchable types of this column.
+ * - A map of expected resolved projections.
+ */
+[[nodiscard]] auto generate_projections(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+)
+        -> std::pair<
+                std::vector<std::pair<std::string, LiteralTypeBitmask>>,
+                std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param column_resolutions
+ * @return The serialized pairs.
+ */
+[[nodiscard]] auto serialize_column_node_ids_map(
+        std::map<std::string, std::set<SchemaTree::Node::id_t>> const& column_resolutions
+) -> std::string;
+
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>> {
+    std::vector<std::string> matchable_kql_expressions;
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> expected_column_resolutions;
+    for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
+        auto const column_query_with_namespace{fmt::format("{}{}", column_namespace, column_query)};
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Int
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestInt)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Float
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {:.2f}", column_query_with_namespace, cRefTestFloat)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Bool
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestBool)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Str
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestStr)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+    }
+
+    auto const single_wildcard{fmt::format("{}{}", column_namespace, "*")};
+    if (expected_column_resolutions.contains(single_wildcard)) {
+        // NOTE: The current implementation doesn't resolve single wildcard.
+        expected_column_resolutions.erase(single_wildcard);
+    }
+    return {std::move(matchable_kql_expressions), std::move(expected_column_resolutions)};
+}
path: components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Consider refactoring repetitive code in generate_matchable_kql_expressions**

The function contains repeated blocks of nearly identical code for each data type (Int, Float, Bool, Str). This repetition increases maintenance burden and risk of inconsistencies.

You could refactor this using a template method or a helper function:

```diff
[[nodiscard]] auto generate_matchable_kql_expressions(
        std::string_view column_namespace,
        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>> {
    std::vector<std::string> matchable_kql_expressions;
    std::map<std::string, std::set<SchemaTree::Node::id_t>> expected_column_resolutions;
+    
+    // Helper to reduce repetition
+    auto process_type = [&](SchemaTree::Node::Type type, auto ref_value, std::string_view format = "{}") {
+        for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
+            auto const column_query_with_namespace{fmt::format("{}{}", column_namespace, column_query)};
+            if (auto const matchable_node_ids{
+                        possible_matches.get_matchable_node_ids_from_schema_tree_type(type)
+                    };
+                    false == matchable_node_ids.empty())
+            {
+                matchable_kql_expressions.emplace_back(
+                        fmt::format("{}: " + std::string{format}, column_query_with_namespace, ref_value)
+                );
+                auto [it, inserted] = expected_column_resolutions.try_emplace(
+                        column_query_with_namespace,
+                        std::set<SchemaTree::Node::id_t>{}
+                );
+                auto& resolved_node_ids{it->second};
+                resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+            }
+        }
+    };
+    
+    // Process each type
+    process_type(SchemaTree::Node::Type::Int, cRefTestInt);
+    process_type(SchemaTree::Node::Type::Float, cRefTestFloat, "{:.2f}");
+    process_type(SchemaTree::Node::Type::Bool, cRefTestBool);
+    process_type(SchemaTree::Node::Type::Str, cRefTestStr);

    auto const single_wildcard{fmt::format("{}{}", column_namespace, "*")};
    if (expected_column_resolutions.contains(single_wildcard)) {
        // NOTE: The current implementation doesn't resolve single wildcard.
        expected_column_resolutions.erase(single_wildcard);
    }
    return {std::move(matchable_kql_expressions), std::move(expected_column_resolutions)};
}
```


> Committable suggestion skipped: line range outside the PR's diff.

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-04T20:53:48Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,332 @@
+#include "QueryHandlerImpl.hpp"
+
+#include <exception>
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/archive_constants.hpp"
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/ConvertToExists.hpp"
+#include "../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../clp_s/search/ast/NarrowTypes.hpp"
+#include "../../../../clp_s/search/ast/OrOfAndForm.hpp"
+#include "../../../../clp_s/search/ast/SearchUtils.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::EmptyExpr;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Pre-processes a search query by applying several transformation passes.
+ * @param query
+ * @return A result containing the transformed
+ * - ErrorCodeEnum::QueryExpressionIsNull if `query` is nullptr.
+ * - ErrorCodeEnum::QueryTransformationPassFailed if any of the transformation pass failed.
+ */
+[[nodiscard]] auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>>;
+
+/**
+ * Creates projected columns and column-to-original-key map from the given projections.
+ * @param projections
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - A vector of projected columns.
+ *   - A projected-column-to-original-key map.
+ * - The possible error codes:
+ *   - ErrorCodeEnum::DuplicateProjectedColumn if `projections` contains duplicated keys.
+ *   - ErrorCodeEnum::ColumnTokenizationFailure if failed to tokenize a projection key.
+ *   - ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure if failed to create a column
+ *     descriptor for the projected key.
+ */
+[[nodiscard]] auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>>;
+
+/**
+ * Creates initial partial resolutions for the given query and projections.
+ * @param query
+ * @param projected_column_to_original_key
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - The partial resolution for auto-generated keys.
+ *   - The partial resolution for user-generated keys.
+ * - The possible error codes:
+ *   - Forwards `initialize_partial_resolution_from_search_ast`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::create`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::next`'s return values.
+ */
+[[nodiscard]] auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>>;
+
+/**
+ * Initializes partial resolutions from a search AST.
+ * @param root The root of the search AST.
+ * @param auto_gen_namespace_partial_resolutions Returns initialized auto-gen partial resolutions.
+ * @param user_gen_namespace_partial_resolutions Returns initialized user-gen partial resolutions.
+ * @return A void result on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::AstDynamicCastFailure if failed to dynamically cast an AST node to a target
+ *   type.
+ */
+[[nodiscard]] auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void>;
+
+/**
+ * @param key_namespace
+ * @return Whether `key_namespace` is auto-generated or user-generated, or std::nullopt if the
+ * namespace is unrecognized.
+ */
+[[nodiscard]] auto is_auto_generated(std::string_view key_namespace) -> std::optional<bool>;
+
+auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>> {
+    if (nullptr == query) {
+        return ErrorCode{ErrorCodeEnum::QueryExpressionIsNull};
+    }
+
+    if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(query)) {
+        return query;
+    }
+
+    try {
+        if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        if (query = clp_s::search::ast::NarrowTypes{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        return clp_s::search::ast::ConvertToExists{}.run(query);
+    } catch (std::exception const& ex) {
+        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+    }
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Preserve the root-cause message when a transformation pass throws**  

Catching `std::exception` and returning a generic `QueryTransformationPassFailed` error drops the diagnostic text in `ex.what()`, making it harder to troubleshoot malformed queries.  Even a lightweight log would help:

```diff
} catch (std::exception const& ex) {
-    return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+    spdlog::debug("Query preprocessing failed: {}", ex.what());   // or embed in ErrorCode
+    return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
}
```

If `ErrorCode` can carry an optional message, propagating `ex.what()` would be even better.




<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    } catch (std::exception const& ex) {
        // Preserve root-cause for easier troubleshooting
        spdlog::debug("Query preprocessing failed: {}", ex.what());
        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
    }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-04T21:06:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,462 @@
+#include <map>
+#include <memory>
+#include <set>
+#include <sstream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <catch2/catch.hpp>
+#include <fmt/core.h>
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/archive_constants.hpp"
+#include "../../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../../clp_s/search/kql/kql.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../../../Value.hpp"
+#include "../QueryHandlerImpl.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::constants::cAutogenNamespace;
+using clp_s::constants::cDefaultNamespace;
+using clp_s::constants::cReservedNamespace1;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+constexpr std::string_view cRefTestStr{"*test*"};
+constexpr value_int_t cRefTestInt{0};
+constexpr value_float_t cRefTestFloat{0.0};
+constexpr value_bool_t cRefTestBool{false};
+
+/**
+ * Generates all the matchable KQL expressions based on the column queries and their matchable
+ * types. For each column query `C` matched type `T`, we generate an expression of `C: cRefTestT`,
+ * where `cRefTestT` is any of the constants defined above.
+ * @param column_namespace The namespace of the column.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - A vector of matchable KQL expressions.
+ * - A map of expected column resolutions.
+ */
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param column_namespace The namespace of the column.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - Generated projections as pairs, where each pair consists of:
+ *   - A projectable column from `column_query_to_possible_matches` that has no wildcard.
+ *   - All the matchable types of this column.
+ * - A map of expected resolved projections.
+ */
+[[nodiscard]] auto generate_projections(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+)
+        -> std::pair<
+                std::vector<std::pair<std::string, LiteralTypeBitmask>>,
+                std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param column_resolutions
+ * @return The serialized pairs.
+ */
+[[nodiscard]] auto serialize_column_node_ids_map(
+        std::map<std::string, std::set<SchemaTree::Node::id_t>> const& column_resolutions
+) -> std::string;
+
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>> {
+    std::vector<std::string> matchable_kql_expressions;
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> expected_column_resolutions;
+    for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
+        auto const column_query_with_namespace{fmt::format("{}{}", column_namespace, column_query)};
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Int
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestInt)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Float
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {:.2f}", column_query_with_namespace, cRefTestFloat)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Bool
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestBool)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Str
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestStr)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+    }
+
+    auto const single_wildcard{fmt::format("{}{}", column_namespace, "*")};
+    if (expected_column_resolutions.contains(single_wildcard)) {
+        // NOTE: The current implementation doesn't resolve single wildcard.
+        expected_column_resolutions.erase(single_wildcard);
+    }
+    return {std::move(matchable_kql_expressions), std::move(expected_column_resolutions)};
+}
+
+auto generate_projections(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+)
+        -> std::pair<
+                std::vector<std::pair<std::string, LiteralTypeBitmask>>,
+                std::map<std::string, std::set<SchemaTree::Node::id_t>>> {
+    std::vector<std::pair<std::string, LiteralTypeBitmask>> projections;
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> expected_resolved_projections;
+    for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
+        if (column_query.find('*') != std::string::npos) {
+            continue;
+        }
+        auto const projectable_node_ids{possible_matches.get_projectable_node_ids()};
+        if (projectable_node_ids.empty()) {
+            continue;
+        }
+        auto const column_query_with_namespace{fmt::format("{}{}", column_namespace, column_query)};
+        projections.emplace_back(
+                column_query_with_namespace,
+                possible_matches.get_matchable_types()
+        );
+        expected_resolved_projections.emplace(
+                column_query_with_namespace,
+                std::set<SchemaTree::Node::id_t>{
+                        projectable_node_ids.cbegin(),
+                        projectable_node_ids.cend()
+                }
+        );
+    }
+    return {std::move(projections), std::move(expected_resolved_projections)};
+}
+
+auto serialize_column_node_ids_map(
+        std::map<std::string, std::set<SchemaTree::Node::id_t>> const& column_resolutions
+) -> std::string {
+    std::string result;
+    for (auto const& [column, resolved_node_ids] : column_resolutions) {
+        result += fmt::format("{}: {}\n", column, fmt::join(resolved_node_ids, ","));
+    }
+    return result;
+}
+}  // namespace
+
+TEST_CASE(
+        "query_handler_update_partially_resolved_columns",
+        "[ffi][ir_stream][search][QueryHandler]"
+) {
+    /*
+     * <0:root:Obj>
+     *      |
+     *      |------------> <1:a:Obj>
+     *      |                  |
+     *      |--> <2:b:Int>     |--> <3:b:Obj>
+     *      |                  |        |
+     *      |--> <12:a:Int>    |        |------------> <4:c:Obj>
+     *                         |        |                  |
+     *                         |        |--> <5:d:Str>     |--> <7:a:Str>
+     *                         |        |                  |
+     *                         |        |--> <6:d:Bool>    |--> <8:d:Str>
+     *                         |        |                  |
+     *                         |        |--> <10:e:Obj>    |--> <9:d:Float>
+     *                         |                           |
+     *                         |--> <13:b:Bool>            |--> <11:f:Obj>
+     */
path: components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp
user: login: LinZhihao-723
body: 12 are in the other branch. It's in this diagram but not the same line in the file.
created_at: 2025-05-04T21:07:42Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,462 @@
+#include <map>
+#include <memory>
+#include <set>
+#include <sstream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <catch2/catch.hpp>
+#include <fmt/core.h>
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/archive_constants.hpp"
+#include "../../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../../clp_s/search/kql/kql.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../../../Value.hpp"
+#include "../QueryHandlerImpl.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::constants::cAutogenNamespace;
+using clp_s::constants::cDefaultNamespace;
+using clp_s::constants::cReservedNamespace1;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+constexpr std::string_view cRefTestStr{"*test*"};
+constexpr value_int_t cRefTestInt{0};
+constexpr value_float_t cRefTestFloat{0.0};
+constexpr value_bool_t cRefTestBool{false};
+
+/**
+ * Generates all the matchable KQL expressions based on the column queries and their matchable
+ * types. For each column query `C` matched type `T`, we generate an expression of `C: cRefTestT`,
+ * where `cRefTestT` is any of the constants defined above.
+ * @param column_namespace The namespace of the column.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - A vector of matchable KQL expressions.
+ * - A map of expected column resolutions.
+ */
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param column_namespace The namespace of the column.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - Generated projections as pairs, where each pair consists of:
+ *   - A projectable column from `column_query_to_possible_matches` that has no wildcard.
+ *   - All the matchable types of this column.
+ * - A map of expected resolved projections.
+ */
+[[nodiscard]] auto generate_projections(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+)
+        -> std::pair<
+                std::vector<std::pair<std::string, LiteralTypeBitmask>>,
+                std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param column_resolutions
+ * @return The serialized pairs.
+ */
+[[nodiscard]] auto serialize_column_node_ids_map(
+        std::map<std::string, std::set<SchemaTree::Node::id_t>> const& column_resolutions
+) -> std::string;
+
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>> {
+    std::vector<std::string> matchable_kql_expressions;
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> expected_column_resolutions;
+    for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
+        auto const column_query_with_namespace{fmt::format("{}{}", column_namespace, column_query)};
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Int
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestInt)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Float
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {:.2f}", column_query_with_namespace, cRefTestFloat)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Bool
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestBool)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Str
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestStr)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+    }
+
+    auto const single_wildcard{fmt::format("{}{}", column_namespace, "*")};
+    if (expected_column_resolutions.contains(single_wildcard)) {
+        // NOTE: The current implementation doesn't resolve single wildcard.
+        expected_column_resolutions.erase(single_wildcard);
+    }
+    return {std::move(matchable_kql_expressions), std::move(expected_column_resolutions)};
+}
+
+auto generate_projections(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+)
+        -> std::pair<
+                std::vector<std::pair<std::string, LiteralTypeBitmask>>,
+                std::map<std::string, std::set<SchemaTree::Node::id_t>>> {
+    std::vector<std::pair<std::string, LiteralTypeBitmask>> projections;
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> expected_resolved_projections;
+    for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
+        if (column_query.find('*') != std::string::npos) {
+            continue;
+        }
+        auto const projectable_node_ids{possible_matches.get_projectable_node_ids()};
+        if (projectable_node_ids.empty()) {
+            continue;
+        }
+        auto const column_query_with_namespace{fmt::format("{}{}", column_namespace, column_query)};
+        projections.emplace_back(
+                column_query_with_namespace,
+                possible_matches.get_matchable_types()
+        );
+        expected_resolved_projections.emplace(
+                column_query_with_namespace,
+                std::set<SchemaTree::Node::id_t>{
+                        projectable_node_ids.cbegin(),
+                        projectable_node_ids.cend()
+                }
+        );
+    }
+    return {std::move(projections), std::move(expected_resolved_projections)};
+}
+
+auto serialize_column_node_ids_map(
+        std::map<std::string, std::set<SchemaTree::Node::id_t>> const& column_resolutions
+) -> std::string {
+    std::string result;
+    for (auto const& [column, resolved_node_ids] : column_resolutions) {
+        result += fmt::format("{}: {}\n", column, fmt::join(resolved_node_ids, ","));
+    }
+    return result;
+}
+}  // namespace
+
+TEST_CASE(
+        "query_handler_update_partially_resolved_columns",
+        "[ffi][ir_stream][search][QueryHandler]"
+) {
+    /*
+     * <0:root:Obj>
+     *      |
+     *      |------------> <1:a:Obj>
+     *      |                  |
+     *      |--> <2:b:Int>     |--> <3:b:Obj>
+     *      |                  |        |
+     *      |--> <12:a:Int>    |        |------------> <4:c:Obj>
+     *                         |        |                  |
+     *                         |        |--> <5:d:Str>     |--> <7:a:Str>
+     *                         |        |                  |
+     *                         |        |--> <6:d:Bool>    |--> <8:d:Str>
+     *                         |        |                  |
+     *                         |        |--> <10:e:Obj>    |--> <9:d:Float>
+     *                         |                           |
+     *                         |--> <13:b:Bool>            |--> <11:f:Obj>
+     */
+    auto const schema_tree{std::make_shared<SchemaTree>()};
+    std::vector<SchemaTree::NodeLocator> const locators{
+            {SchemaTree::cRootId, "a", SchemaTree::Node::Type::Obj},
+            {SchemaTree::cRootId, "b", SchemaTree::Node::Type::Int},
+            {1, "b", SchemaTree::Node::Type::Obj},
+            {3, "c", SchemaTree::Node::Type::Obj},
+            {3, "d", SchemaTree::Node::Type::Str},
+            {3, "d", SchemaTree::Node::Type::Bool},
+            {4, "a", SchemaTree::Node::Type::Str},
+            {4, "d", SchemaTree::Node::Type::Str},
+            {4, "d", SchemaTree::Node::Type::Float},
+            {3, "e", SchemaTree::Node::Type::Obj},
+            {4, "f", SchemaTree::Node::Type::Obj},
+            {SchemaTree::cRootId, "a", SchemaTree::Node::Type::Int},
+            {1, "b", SchemaTree::Node::Type::Bool}
+    };
+    for (auto const& locator : locators) {
+        REQUIRE_NOTHROW(schema_tree->insert_node(locator));
+    }
+
+    auto const column_query_to_possible_matches{get_schema_tree_column_queries(schema_tree)};
+    CAPTURE(column_query_to_possible_matches);
+
+    auto const is_auto_generated = GENERATE(true, false);
+    auto const [matchable_kql_expressions, expected_column_resolutions]
+            = generate_matchable_kql_expressions(
+                    is_auto_generated ? cAutogenNamespace : cDefaultNamespace,
+                    column_query_to_possible_matches
+            );
+    auto const matchable_kql_expressions_with_unrecognized_namespace{
+            generate_matchable_kql_expressions(
+                    cReservedNamespace1,
+                    column_query_to_possible_matches
+            )
+                    .first
+    };
+
+    constexpr std::string_view cUnmatchableNodeName{"unknown"};
+    for (auto const& locator : locators) {
+        REQUIRE((locator.get_key_name() != cUnmatchableNodeName));
+    }
+    std::vector<std::string> unmatchable_kql_expressions;
+    for (auto const& [matchable_column, resolved_node_ids] : expected_column_resolutions) {
+        unmatchable_kql_expressions.emplace_back(
+                fmt::format("{}.{}: {}", matchable_column, cUnmatchableNodeName, cRefTestInt)
+        );
+    }
+
+    auto const matchable_kql_query_str{
+            fmt::format("{}", fmt::join(matchable_kql_expressions, " OR "))
+    };
+    auto const unmatchable_kql_query_str{
+            fmt::format("{}", fmt::join(unmatchable_kql_expressions, " OR "))
+    };
+    auto const unrecognized_namespace_kql_query_str{fmt::format(
+            "{}",
+            fmt::join(matchable_kql_expressions_with_unrecognized_namespace, " OR ")
+    )};
+    auto const kql_query_str{fmt::format(
+            "{} OR {} OR {}",
+            matchable_kql_query_str,
+            unmatchable_kql_query_str,
+            unrecognized_namespace_kql_query_str
+    )};
+    CAPTURE(kql_query_str);
+
+    auto query_stream{std::istringstream{kql_query_str}};
+    auto query{clp_s::search::kql::parse_kql_expression(query_stream)};
+
+    auto query_handler_impl_result{QueryHandlerImpl::create(query, {}, true)};
+    // We disabled the check to silent clang-tidy warnings on `outcome`'s source files.
+    // Related issues: https://github.com/ned14/outcome/issues/311
+    // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)
+    REQUIRE_FALSE(query_handler_impl_result.has_error());
+    auto& query_handler_impl{query_handler_impl_result.value()};
+
+    SchemaTree::Node::id_t node_id{1};
+    for (auto const& locator : locators) {
+        REQUIRE_FALSE(query_handler_impl
+                              .update_partially_resolved_columns(
+                                      true,
+                                      locator,
+                                      node_id,
+                                      trivial_new_projected_schema_tree_node_callback
+                              )
+                              .has_error());
+        REQUIRE_FALSE(query_handler_impl
+                              .update_partially_resolved_columns(
+                                      false,
+                                      locator,
+                                      node_id,
+                                      trivial_new_projected_schema_tree_node_callback
+                              )
+                              .has_error());
+        ++node_id;
+    }
+
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> actual_column_resolutions;
+    for (auto const& [column_descriptor, resolved_nodes] :
+         query_handler_impl.get_resolved_column_to_schema_tree_node_ids())
+    {
+        REQUIRE(
+                (column_descriptor->get_namespace() == cDefaultNamespace
+                 || column_descriptor->get_namespace() == cAutogenNamespace)
+        );
+        std::vector<std::string_view> tokens;
+        for (auto const& token : column_descriptor->get_descriptor_list()) {
+            tokens.emplace_back(token.get_token());
+        }
+        auto const original_column_query{
+                fmt::format("{}{}", column_descriptor->get_namespace(), fmt::join(tokens, "."))
+        };
+        auto [it, inserted] = actual_column_resolutions.try_emplace(
+                original_column_query,
+                std::set<SchemaTree::Node::id_t>{}
+        );
+        auto& all_resolved_nodes{it->second};
+        all_resolved_nodes.insert(resolved_nodes.cbegin(), resolved_nodes.cend());
+    }
+
+    CAPTURE("\n" + serialize_column_node_ids_map(actual_column_resolutions));
+    CAPTURE("\n" + serialize_column_node_ids_map(expected_column_resolutions));
+
+    REQUIRE((actual_column_resolutions == expected_column_resolutions));
+}
path: components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp
user: login: LinZhihao-723
body: Don't think it's necessary now. We may soon add more test trees.
created_at: 2025-05-04T21:08:20Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,462 @@
+#include <map>
+#include <memory>
+#include <set>
+#include <sstream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <catch2/catch.hpp>
+#include <fmt/core.h>
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/archive_constants.hpp"
+#include "../../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../../clp_s/search/kql/kql.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../../../Value.hpp"
+#include "../QueryHandlerImpl.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::constants::cAutogenNamespace;
+using clp_s::constants::cDefaultNamespace;
+using clp_s::constants::cReservedNamespace1;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+constexpr std::string_view cRefTestStr{"*test*"};
+constexpr value_int_t cRefTestInt{0};
+constexpr value_float_t cRefTestFloat{0.0};
+constexpr value_bool_t cRefTestBool{false};
+
+/**
+ * Generates all the matchable KQL expressions based on the column queries and their matchable
+ * types. For each column query `C` matched type `T`, we generate an expression of `C: cRefTestT`,
+ * where `cRefTestT` is any of the constants defined above.
+ * @param column_namespace The namespace of the column.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - A vector of matchable KQL expressions.
+ * - A map of expected column resolutions.
+ */
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param column_namespace The namespace of the column.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - Generated projections as pairs, where each pair consists of:
+ *   - A projectable column from `column_query_to_possible_matches` that has no wildcard.
+ *   - All the matchable types of this column.
+ * - A map of expected resolved projections.
+ */
+[[nodiscard]] auto generate_projections(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+)
+        -> std::pair<
+                std::vector<std::pair<std::string, LiteralTypeBitmask>>,
+                std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param column_resolutions
+ * @return The serialized pairs.
+ */
+[[nodiscard]] auto serialize_column_node_ids_map(
+        std::map<std::string, std::set<SchemaTree::Node::id_t>> const& column_resolutions
+) -> std::string;
+
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>> {
+    std::vector<std::string> matchable_kql_expressions;
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> expected_column_resolutions;
+    for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
+        auto const column_query_with_namespace{fmt::format("{}{}", column_namespace, column_query)};
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Int
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestInt)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Float
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {:.2f}", column_query_with_namespace, cRefTestFloat)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Bool
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestBool)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Str
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestStr)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+    }
+
+    auto const single_wildcard{fmt::format("{}{}", column_namespace, "*")};
+    if (expected_column_resolutions.contains(single_wildcard)) {
+        // NOTE: The current implementation doesn't resolve single wildcard.
+        expected_column_resolutions.erase(single_wildcard);
+    }
+    return {std::move(matchable_kql_expressions), std::move(expected_column_resolutions)};
+}
+
+auto generate_projections(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+)
+        -> std::pair<
+                std::vector<std::pair<std::string, LiteralTypeBitmask>>,
+                std::map<std::string, std::set<SchemaTree::Node::id_t>>> {
+    std::vector<std::pair<std::string, LiteralTypeBitmask>> projections;
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> expected_resolved_projections;
+    for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
+        if (column_query.find('*') != std::string::npos) {
+            continue;
+        }
+        auto const projectable_node_ids{possible_matches.get_projectable_node_ids()};
+        if (projectable_node_ids.empty()) {
+            continue;
+        }
+        auto const column_query_with_namespace{fmt::format("{}{}", column_namespace, column_query)};
+        projections.emplace_back(
+                column_query_with_namespace,
+                possible_matches.get_matchable_types()
+        );
+        expected_resolved_projections.emplace(
+                column_query_with_namespace,
+                std::set<SchemaTree::Node::id_t>{
+                        projectable_node_ids.cbegin(),
+                        projectable_node_ids.cend()
+                }
+        );
+    }
+    return {std::move(projections), std::move(expected_resolved_projections)};
+}
+
+auto serialize_column_node_ids_map(
+        std::map<std::string, std::set<SchemaTree::Node::id_t>> const& column_resolutions
+) -> std::string {
+    std::string result;
+    for (auto const& [column, resolved_node_ids] : column_resolutions) {
+        result += fmt::format("{}: {}\n", column, fmt::join(resolved_node_ids, ","));
+    }
+    return result;
+}
+}  // namespace
+
+TEST_CASE(
+        "query_handler_update_partially_resolved_columns",
+        "[ffi][ir_stream][search][QueryHandler]"
+) {
+    /*
+     * <0:root:Obj>
+     *      |
+     *      |------------> <1:a:Obj>
+     *      |                  |
+     *      |--> <2:b:Int>     |--> <3:b:Obj>
+     *      |                  |        |
+     *      |--> <12:a:Int>    |        |------------> <4:c:Obj>
+     *                         |        |                  |
+     *                         |        |--> <5:d:Str>     |--> <7:a:Str>
+     *                         |        |                  |
+     *                         |        |--> <6:d:Bool>    |--> <8:d:Str>
+     *                         |        |                  |
+     *                         |        |--> <10:e:Obj>    |--> <9:d:Float>
+     *                         |                           |
+     *                         |--> <13:b:Bool>            |--> <11:f:Obj>
+     */
path: components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: <1:a:Obj>
       |                  |
       |--> <2:b:Int>     |--> <3:b:Obj>
       |                  |        |
       |--> <12:a:Int>    |        |------------> <4:c:Obj>
  ```
  And later:
  ```
                          |--> <13:b:Bool>            |--> <11:f:Obj>
  ```

[analysis]
The user is correct. Both nodes 12 and 13 are indeed represented in the diagram:
- Node 12 (`<12:a:Int>`) is shown as a direct child of the root node on the left branch.
- Node 13 (`<13:b:Bool>`) is shown as a child of node 1 at the bottom of the diagram.

The auto-generated comment was incorrect in suggesting these nodes were missing from the diagram.

[actions]
I will acknowledge that the user is right and that our automated comment was incorrect. I'll apologize for the confusion and confirm that the diagram is actually accurate.
-->

@LinZhihao-723 You're absolutely right. Looking more carefully at the diagram, I can see that node 12 (`<12:a:Int>`) is properly represented as a child of the root node, and node 13 (`<13:b:Bool>`) is shown as a child of node 1. The diagram is accurate and matches the code implementation. I apologize for the confusion from our automated review comment.

🐰 *embarrassed bunny noises*


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer><1:a:Obj>
       |                  |
       |--&gt; <2:b:Int>     |--&gt; <3:b:Obj>
       |                  |        |
       |--&gt; <12:a:Int>    |        |------------&gt; <4:c:Obj>
  ```
  And later:
  ```
                          |--&gt; <13:b:Bool>            |--&gt; <11:f:Obj>
  ```

[analysis]
The user is correct. Both nodes 12 and 13 are indeed represented in the diagram:
- Node 12 (`<12:a:Int>`) is shown as a direct child of the root node on the left branch.
- Node 13 (`<13:b:Bool>`) is shown as a child of node 1 at the bottom of the diagram.

The auto-generated comment was incorrect in suggesting these nodes were missing from the diagram.

[actions]
I will acknowledge that the user is right and that our automated comment was incorrect. I'll apologize for the confusion and confirm that the diagram is actually accurate.
--&gt;

@LinZhihao-723 You're absolutely right. Looking more carefully at the diagram, I can see that node 12 (`<12:a:Int>`) is properly represented as a child of the root node, and node 13 (`<13:b:Bool>`) is shown as a child of node 1. The diagram is accurate and matches the code implementation. I apologize for the confusion from our automated review comment.

🐰 *embarrassed bunny noises*</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-04T21:08:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,462 @@
+#include <map>
+#include <memory>
+#include <set>
+#include <sstream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <catch2/catch.hpp>
+#include <fmt/core.h>
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/archive_constants.hpp"
+#include "../../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../../clp_s/search/kql/kql.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../../../Value.hpp"
+#include "../QueryHandlerImpl.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::constants::cAutogenNamespace;
+using clp_s::constants::cDefaultNamespace;
+using clp_s::constants::cReservedNamespace1;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+constexpr std::string_view cRefTestStr{"*test*"};
+constexpr value_int_t cRefTestInt{0};
+constexpr value_float_t cRefTestFloat{0.0};
+constexpr value_bool_t cRefTestBool{false};
+
+/**
+ * Generates all the matchable KQL expressions based on the column queries and their matchable
+ * types. For each column query `C` matched type `T`, we generate an expression of `C: cRefTestT`,
+ * where `cRefTestT` is any of the constants defined above.
+ * @param column_namespace The namespace of the column.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - A vector of matchable KQL expressions.
+ * - A map of expected column resolutions.
+ */
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param column_namespace The namespace of the column.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - Generated projections as pairs, where each pair consists of:
+ *   - A projectable column from `column_query_to_possible_matches` that has no wildcard.
+ *   - All the matchable types of this column.
+ * - A map of expected resolved projections.
+ */
+[[nodiscard]] auto generate_projections(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+)
+        -> std::pair<
+                std::vector<std::pair<std::string, LiteralTypeBitmask>>,
+                std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param column_resolutions
+ * @return The serialized pairs.
+ */
+[[nodiscard]] auto serialize_column_node_ids_map(
+        std::map<std::string, std::set<SchemaTree::Node::id_t>> const& column_resolutions
+) -> std::string;
+
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>> {
+    std::vector<std::string> matchable_kql_expressions;
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> expected_column_resolutions;
+    for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
+        auto const column_query_with_namespace{fmt::format("{}{}", column_namespace, column_query)};
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Int
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestInt)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Float
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {:.2f}", column_query_with_namespace, cRefTestFloat)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Bool
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestBool)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Str
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestStr)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+    }
+
+    auto const single_wildcard{fmt::format("{}{}", column_namespace, "*")};
+    if (expected_column_resolutions.contains(single_wildcard)) {
+        // NOTE: The current implementation doesn't resolve single wildcard.
+        expected_column_resolutions.erase(single_wildcard);
+    }
+    return {std::move(matchable_kql_expressions), std::move(expected_column_resolutions)};
+}
path: components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp
user: login: LinZhihao-723
body: Don't think this is necessary.
created_at: 2025-05-04T21:08:40Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,462 @@
+#include <map>
+#include <memory>
+#include <set>
+#include <sstream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <catch2/catch.hpp>
+#include <fmt/core.h>
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/archive_constants.hpp"
+#include "../../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../../clp_s/search/kql/kql.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../../../Value.hpp"
+#include "../QueryHandlerImpl.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::constants::cAutogenNamespace;
+using clp_s::constants::cDefaultNamespace;
+using clp_s::constants::cReservedNamespace1;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+constexpr std::string_view cRefTestStr{"*test*"};
+constexpr value_int_t cRefTestInt{0};
+constexpr value_float_t cRefTestFloat{0.0};
+constexpr value_bool_t cRefTestBool{false};
+
+/**
+ * Generates all the matchable KQL expressions based on the column queries and their matchable
+ * types. For each column query `C` matched type `T`, we generate an expression of `C: cRefTestT`,
+ * where `cRefTestT` is any of the constants defined above.
+ * @param column_namespace The namespace of the column.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - A vector of matchable KQL expressions.
+ * - A map of expected column resolutions.
+ */
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param column_namespace The namespace of the column.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - Generated projections as pairs, where each pair consists of:
+ *   - A projectable column from `column_query_to_possible_matches` that has no wildcard.
+ *   - All the matchable types of this column.
+ * - A map of expected resolved projections.
+ */
+[[nodiscard]] auto generate_projections(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+)
+        -> std::pair<
+                std::vector<std::pair<std::string, LiteralTypeBitmask>>,
+                std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param column_resolutions
+ * @return The serialized pairs.
+ */
+[[nodiscard]] auto serialize_column_node_ids_map(
+        std::map<std::string, std::set<SchemaTree::Node::id_t>> const& column_resolutions
+) -> std::string;
+
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>> {
+    std::vector<std::string> matchable_kql_expressions;
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> expected_column_resolutions;
+    for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
+        auto const column_query_with_namespace{fmt::format("{}{}", column_namespace, column_query)};
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Int
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestInt)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Float
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {:.2f}", column_query_with_namespace, cRefTestFloat)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Bool
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestBool)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Str
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestStr)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+    }
+
+    auto const single_wildcard{fmt::format("{}{}", column_namespace, "*")};
+    if (expected_column_resolutions.contains(single_wildcard)) {
+        // NOTE: The current implementation doesn't resolve single wildcard.
+        expected_column_resolutions.erase(single_wildcard);
+    }
+    return {std::move(matchable_kql_expressions), std::move(expected_column_resolutions)};
+}
+
+auto generate_projections(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+)
+        -> std::pair<
+                std::vector<std::pair<std::string, LiteralTypeBitmask>>,
+                std::map<std::string, std::set<SchemaTree::Node::id_t>>> {
+    std::vector<std::pair<std::string, LiteralTypeBitmask>> projections;
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> expected_resolved_projections;
+    for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
+        if (column_query.find('*') != std::string::npos) {
+            continue;
+        }
+        auto const projectable_node_ids{possible_matches.get_projectable_node_ids()};
+        if (projectable_node_ids.empty()) {
+            continue;
+        }
+        auto const column_query_with_namespace{fmt::format("{}{}", column_namespace, column_query)};
+        projections.emplace_back(
+                column_query_with_namespace,
+                possible_matches.get_matchable_types()
+        );
+        expected_resolved_projections.emplace(
+                column_query_with_namespace,
+                std::set<SchemaTree::Node::id_t>{
+                        projectable_node_ids.cbegin(),
+                        projectable_node_ids.cend()
+                }
+        );
+    }
+    return {std::move(projections), std::move(expected_resolved_projections)};
+}
+
+auto serialize_column_node_ids_map(
+        std::map<std::string, std::set<SchemaTree::Node::id_t>> const& column_resolutions
+) -> std::string {
+    std::string result;
+    for (auto const& [column, resolved_node_ids] : column_resolutions) {
+        result += fmt::format("{}: {}\n", column, fmt::join(resolved_node_ids, ","));
+    }
+    return result;
+}
+}  // namespace
+
+TEST_CASE(
+        "query_handler_update_partially_resolved_columns",
+        "[ffi][ir_stream][search][QueryHandler]"
+) {
+    /*
+     * <0:root:Obj>
+     *      |
+     *      |------------> <1:a:Obj>
+     *      |                  |
+     *      |--> <2:b:Int>     |--> <3:b:Obj>
+     *      |                  |        |
+     *      |--> <12:a:Int>    |        |------------> <4:c:Obj>
+     *                         |        |                  |
+     *                         |        |--> <5:d:Str>     |--> <7:a:Str>
+     *                         |        |                  |
+     *                         |        |--> <6:d:Bool>    |--> <8:d:Str>
+     *                         |        |                  |
+     *                         |        |--> <10:e:Obj>    |--> <9:d:Float>
+     *                         |                           |
+     *                         |--> <13:b:Bool>            |--> <11:f:Obj>
+     */
+    auto const schema_tree{std::make_shared<SchemaTree>()};
+    std::vector<SchemaTree::NodeLocator> const locators{
+            {SchemaTree::cRootId, "a", SchemaTree::Node::Type::Obj},
+            {SchemaTree::cRootId, "b", SchemaTree::Node::Type::Int},
+            {1, "b", SchemaTree::Node::Type::Obj},
+            {3, "c", SchemaTree::Node::Type::Obj},
+            {3, "d", SchemaTree::Node::Type::Str},
+            {3, "d", SchemaTree::Node::Type::Bool},
+            {4, "a", SchemaTree::Node::Type::Str},
+            {4, "d", SchemaTree::Node::Type::Str},
+            {4, "d", SchemaTree::Node::Type::Float},
+            {3, "e", SchemaTree::Node::Type::Obj},
+            {4, "f", SchemaTree::Node::Type::Obj},
+            {SchemaTree::cRootId, "a", SchemaTree::Node::Type::Int},
+            {1, "b", SchemaTree::Node::Type::Bool}
+    };
+    for (auto const& locator : locators) {
+        REQUIRE_NOTHROW(schema_tree->insert_node(locator));
+    }
+
+    auto const column_query_to_possible_matches{get_schema_tree_column_queries(schema_tree)};
+    CAPTURE(column_query_to_possible_matches);
+
+    auto const is_auto_generated = GENERATE(true, false);
+    auto const [matchable_kql_expressions, expected_column_resolutions]
+            = generate_matchable_kql_expressions(
+                    is_auto_generated ? cAutogenNamespace : cDefaultNamespace,
+                    column_query_to_possible_matches
+            );
+    auto const matchable_kql_expressions_with_unrecognized_namespace{
+            generate_matchable_kql_expressions(
+                    cReservedNamespace1,
+                    column_query_to_possible_matches
+            )
+                    .first
+    };
+
+    constexpr std::string_view cUnmatchableNodeName{"unknown"};
+    for (auto const& locator : locators) {
+        REQUIRE((locator.get_key_name() != cUnmatchableNodeName));
+    }
+    std::vector<std::string> unmatchable_kql_expressions;
+    for (auto const& [matchable_column, resolved_node_ids] : expected_column_resolutions) {
+        unmatchable_kql_expressions.emplace_back(
+                fmt::format("{}.{}: {}", matchable_column, cUnmatchableNodeName, cRefTestInt)
+        );
+    }
+
+    auto const matchable_kql_query_str{
+            fmt::format("{}", fmt::join(matchable_kql_expressions, " OR "))
+    };
+    auto const unmatchable_kql_query_str{
+            fmt::format("{}", fmt::join(unmatchable_kql_expressions, " OR "))
+    };
+    auto const unrecognized_namespace_kql_query_str{fmt::format(
+            "{}",
+            fmt::join(matchable_kql_expressions_with_unrecognized_namespace, " OR ")
+    )};
+    auto const kql_query_str{fmt::format(
+            "{} OR {} OR {}",
+            matchable_kql_query_str,
+            unmatchable_kql_query_str,
+            unrecognized_namespace_kql_query_str
+    )};
+    CAPTURE(kql_query_str);
+
+    auto query_stream{std::istringstream{kql_query_str}};
+    auto query{clp_s::search::kql::parse_kql_expression(query_stream)};
+
+    auto query_handler_impl_result{QueryHandlerImpl::create(query, {}, true)};
+    // We disabled the check to silent clang-tidy warnings on `outcome`'s source files.
+    // Related issues: https://github.com/ned14/outcome/issues/311
+    // NOLINTNEXTLINE(clang-analyzer-optin.core.EnumCastOutOfRange)
+    REQUIRE_FALSE(query_handler_impl_result.has_error());
+    auto& query_handler_impl{query_handler_impl_result.value()};
+
+    SchemaTree::Node::id_t node_id{1};
+    for (auto const& locator : locators) {
+        REQUIRE_FALSE(query_handler_impl
+                              .update_partially_resolved_columns(
+                                      true,
+                                      locator,
+                                      node_id,
+                                      trivial_new_projected_schema_tree_node_callback
+                              )
+                              .has_error());
+        REQUIRE_FALSE(query_handler_impl
+                              .update_partially_resolved_columns(
+                                      false,
+                                      locator,
+                                      node_id,
+                                      trivial_new_projected_schema_tree_node_callback
+                              )
+                              .has_error());
+        ++node_id;
+    }
+
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> actual_column_resolutions;
+    for (auto const& [column_descriptor, resolved_nodes] :
+         query_handler_impl.get_resolved_column_to_schema_tree_node_ids())
+    {
+        REQUIRE(
+                (column_descriptor->get_namespace() == cDefaultNamespace
+                 || column_descriptor->get_namespace() == cAutogenNamespace)
+        );
+        std::vector<std::string_view> tokens;
+        for (auto const& token : column_descriptor->get_descriptor_list()) {
+            tokens.emplace_back(token.get_token());
+        }
+        auto const original_column_query{
+                fmt::format("{}{}", column_descriptor->get_namespace(), fmt::join(tokens, "."))
+        };
+        auto [it, inserted] = actual_column_resolutions.try_emplace(
+                original_column_query,
+                std::set<SchemaTree::Node::id_t>{}
+        );
+        auto& all_resolved_nodes{it->second};
+        all_resolved_nodes.insert(resolved_nodes.cbegin(), resolved_nodes.cend());
+    }
+
+    CAPTURE("\n" + serialize_column_node_ids_map(actual_column_resolutions));
+    CAPTURE("\n" + serialize_column_node_ids_map(expected_column_resolutions));
+
+    REQUIRE((actual_column_resolutions == expected_column_resolutions));
+}
path: components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: @LinZhihao-723 That's a reasonable approach. If you're planning to add more test trees soon, it makes sense to hold off on the refactoring until you have a better understanding of all use cases. Let me know if you need any assistance when you're ready to organize those test structures.

(\_/)
(•ᴥ•)
/ >📝


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@LinZhihao-723 That's a reasonable approach. If you're planning to add more test trees soon, it makes sense to hold off on the refactoring until you have a better understanding of all use cases. Let me know if you need any assistance when you're ready to organize those test structures.

(\_/)
(•ᴥ•)
/ >📝</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-04T21:08:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,332 @@
+#include "QueryHandlerImpl.hpp"
+
+#include <exception>
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/archive_constants.hpp"
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/ConvertToExists.hpp"
+#include "../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../clp_s/search/ast/NarrowTypes.hpp"
+#include "../../../../clp_s/search/ast/OrOfAndForm.hpp"
+#include "../../../../clp_s/search/ast/SearchUtils.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::EmptyExpr;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Pre-processes a search query by applying several transformation passes.
+ * @param query
+ * @return A result containing the transformed
+ * - ErrorCodeEnum::QueryExpressionIsNull if `query` is nullptr.
+ * - ErrorCodeEnum::QueryTransformationPassFailed if any of the transformation pass failed.
+ */
+[[nodiscard]] auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>>;
+
+/**
+ * Creates projected columns and column-to-original-key map from the given projections.
+ * @param projections
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - A vector of projected columns.
+ *   - A projected-column-to-original-key map.
+ * - The possible error codes:
+ *   - ErrorCodeEnum::DuplicateProjectedColumn if `projections` contains duplicated keys.
+ *   - ErrorCodeEnum::ColumnTokenizationFailure if failed to tokenize a projection key.
+ *   - ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure if failed to create a column
+ *     descriptor for the projected key.
+ */
+[[nodiscard]] auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>>;
+
+/**
+ * Creates initial partial resolutions for the given query and projections.
+ * @param query
+ * @param projected_column_to_original_key
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - The partial resolution for auto-generated keys.
+ *   - The partial resolution for user-generated keys.
+ * - The possible error codes:
+ *   - Forwards `initialize_partial_resolution_from_search_ast`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::create`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::next`'s return values.
+ */
+[[nodiscard]] auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>>;
+
+/**
+ * Initializes partial resolutions from a search AST.
+ * @param root The root of the search AST.
+ * @param auto_gen_namespace_partial_resolutions Returns initialized auto-gen partial resolutions.
+ * @param user_gen_namespace_partial_resolutions Returns initialized user-gen partial resolutions.
+ * @return A void result on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::AstDynamicCastFailure if failed to dynamically cast an AST node to a target
+ *   type.
+ */
+[[nodiscard]] auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void>;
+
+/**
+ * @param key_namespace
+ * @return Whether `key_namespace` is auto-generated or user-generated, or std::nullopt if the
+ * namespace is unrecognized.
+ */
+[[nodiscard]] auto is_auto_generated(std::string_view key_namespace) -> std::optional<bool>;
+
+auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>> {
+    if (nullptr == query) {
+        return ErrorCode{ErrorCodeEnum::QueryExpressionIsNull};
+    }
+
+    if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(query)) {
+        return query;
+    }
+
+    try {
+        if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        if (query = clp_s::search::ast::NarrowTypes{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        return clp_s::search::ast::ConvertToExists{}.run(query);
+    } catch (std::exception const& ex) {
+        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+    }
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
user: login: LinZhihao-723
body: As already explained in the previous replies, I don't think it's necessary to log anything here.
created_at: 2025-05-04T21:09:05Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,462 @@
+#include <map>
+#include <memory>
+#include <set>
+#include <sstream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <catch2/catch.hpp>
+#include <fmt/core.h>
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/archive_constants.hpp"
+#include "../../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../../clp_s/search/kql/kql.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../../../Value.hpp"
+#include "../QueryHandlerImpl.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::constants::cAutogenNamespace;
+using clp_s::constants::cDefaultNamespace;
+using clp_s::constants::cReservedNamespace1;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+constexpr std::string_view cRefTestStr{"*test*"};
+constexpr value_int_t cRefTestInt{0};
+constexpr value_float_t cRefTestFloat{0.0};
+constexpr value_bool_t cRefTestBool{false};
+
+/**
+ * Generates all the matchable KQL expressions based on the column queries and their matchable
+ * types. For each column query `C` matched type `T`, we generate an expression of `C: cRefTestT`,
+ * where `cRefTestT` is any of the constants defined above.
+ * @param column_namespace The namespace of the column.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - A vector of matchable KQL expressions.
+ * - A map of expected column resolutions.
+ */
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param column_namespace The namespace of the column.
+ * @param column_query_to_possible_matches
+ * @return A pair that consists of:
+ * - Generated projections as pairs, where each pair consists of:
+ *   - A projectable column from `column_query_to_possible_matches` that has no wildcard.
+ *   - All the matchable types of this column.
+ * - A map of expected resolved projections.
+ */
+[[nodiscard]] auto generate_projections(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+)
+        -> std::pair<
+                std::vector<std::pair<std::string, LiteralTypeBitmask>>,
+                std::map<std::string, std::set<SchemaTree::Node::id_t>>>;
+
+/**
+ * @param column_resolutions
+ * @return The serialized pairs.
+ */
+[[nodiscard]] auto serialize_column_node_ids_map(
+        std::map<std::string, std::set<SchemaTree::Node::id_t>> const& column_resolutions
+) -> std::string;
+
+[[nodiscard]] auto generate_matchable_kql_expressions(
+        std::string_view column_namespace,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::pair<std::vector<std::string>, std::map<std::string, std::set<SchemaTree::Node::id_t>>> {
+    std::vector<std::string> matchable_kql_expressions;
+    std::map<std::string, std::set<SchemaTree::Node::id_t>> expected_column_resolutions;
+    for (auto const& [column_query, possible_matches] : column_query_to_possible_matches) {
+        auto const column_query_with_namespace{fmt::format("{}{}", column_namespace, column_query)};
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Int
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestInt)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Float
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {:.2f}", column_query_with_namespace, cRefTestFloat)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Bool
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestBool)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+
+        if (auto const matchable_node_ids{
+                    possible_matches.get_matchable_node_ids_from_schema_tree_type(
+                            SchemaTree::Node::Type::Str
+                    )
+            };
+            false == matchable_node_ids.empty())
+        {
+            matchable_kql_expressions.emplace_back(
+                    fmt::format("{}: {}", column_query_with_namespace, cRefTestStr)
+            );
+            auto [it, inserted] = expected_column_resolutions.try_emplace(
+                    column_query_with_namespace,
+                    std::set<SchemaTree::Node::id_t>{}
+            );
+            auto& resolved_node_ids{it->second};
+            resolved_node_ids.insert(matchable_node_ids.cbegin(), matchable_node_ids.cend());
+        }
+    }
+
+    auto const single_wildcard{fmt::format("{}{}", column_namespace, "*")};
+    if (expected_column_resolutions.contains(single_wildcard)) {
+        // NOTE: The current implementation doesn't resolve single wildcard.
+        expected_column_resolutions.erase(single_wildcard);
+    }
+    return {std::move(matchable_kql_expressions), std::move(expected_column_resolutions)};
+}
path: components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: @LinZhihao-723 Understood! I'll respect your preference to keep the code as is rather than refactoring it. Thanks for reviewing my suggestion.


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@LinZhihao-723 Understood! I'll respect your preference to keep the code as is rather than refactoring it. Thanks for reviewing my suggestion.</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-04T21:09:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,332 @@
+#include "QueryHandlerImpl.hpp"
+
+#include <exception>
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/archive_constants.hpp"
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/ConvertToExists.hpp"
+#include "../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/FilterExpr.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../clp_s/search/ast/NarrowTypes.hpp"
+#include "../../../../clp_s/search/ast/OrOfAndForm.hpp"
+#include "../../../../clp_s/search/ast/SearchUtils.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+
+namespace clp::ffi::ir_stream::search {
+namespace {
+using clp_s::search::ast::ColumnDescriptor;
+using clp_s::search::ast::EmptyExpr;
+using clp_s::search::ast::Expression;
+using clp_s::search::ast::FilterExpr;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+/**
+ * Pre-processes a search query by applying several transformation passes.
+ * @param query
+ * @return A result containing the transformed
+ * - ErrorCodeEnum::QueryExpressionIsNull if `query` is nullptr.
+ * - ErrorCodeEnum::QueryTransformationPassFailed if any of the transformation pass failed.
+ */
+[[nodiscard]] auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>>;
+
+/**
+ * Creates projected columns and column-to-original-key map from the given projections.
+ * @param projections
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - A vector of projected columns.
+ *   - A projected-column-to-original-key map.
+ * - The possible error codes:
+ *   - ErrorCodeEnum::DuplicateProjectedColumn if `projections` contains duplicated keys.
+ *   - ErrorCodeEnum::ColumnTokenizationFailure if failed to tokenize a projection key.
+ *   - ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure if failed to create a column
+ *     descriptor for the projected key.
+ */
+[[nodiscard]] auto create_projected_columns_and_projection_map(
+        std::vector<std::pair<std::string, LiteralTypeBitmask>> const& projections
+)
+        -> outcome_v2::std_result<std::pair<
+                std::vector<std::shared_ptr<ColumnDescriptor>>,
+                QueryHandlerImpl::ProjectionMap>>;
+
+/**
+ * Creates initial partial resolutions for the given query and projections.
+ * @param query
+ * @param projected_column_to_original_key
+ * @return A result containing a pair or an error code indicating the failure:
+ * - The pair:
+ *   - The partial resolution for auto-generated keys.
+ *   - The partial resolution for user-generated keys.
+ * - The possible error codes:
+ *   - Forwards `initialize_partial_resolution_from_search_ast`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::create`'s return values.
+ *   - Forwards `QueryHandlerImpl::ColumnDescriptorTokenIterator::next`'s return values.
+ */
+[[nodiscard]] auto create_initial_partial_resolutions(
+        std::shared_ptr<Expression> const& query,
+        QueryHandlerImpl::ProjectionMap const& projected_column_to_original_key
+)
+        -> outcome_v2::std_result<std::pair<
+                QueryHandlerImpl::PartialResolutionMap,
+                QueryHandlerImpl::PartialResolutionMap>>;
+
+/**
+ * Initializes partial resolutions from a search AST.
+ * @param root The root of the search AST.
+ * @param auto_gen_namespace_partial_resolutions Returns initialized auto-gen partial resolutions.
+ * @param user_gen_namespace_partial_resolutions Returns initialized user-gen partial resolutions.
+ * @return A void result on success, or an error code indicating the failure:
+ * - ErrorCodeEnum::AstDynamicCastFailure if failed to dynamically cast an AST node to a target
+ *   type.
+ */
+[[nodiscard]] auto initialize_partial_resolution_from_search_ast(
+        std::shared_ptr<Expression> const& root,
+        QueryHandlerImpl::PartialResolutionMap& auto_gen_namespace_partial_resolutions,
+        QueryHandlerImpl::PartialResolutionMap& user_gen_namespace_partial_resolutions
+) -> outcome_v2::std_result<void>;
+
+/**
+ * @param key_namespace
+ * @return Whether `key_namespace` is auto-generated or user-generated, or std::nullopt if the
+ * namespace is unrecognized.
+ */
+[[nodiscard]] auto is_auto_generated(std::string_view key_namespace) -> std::optional<bool>;
+
+auto preprocess_query(std::shared_ptr<Expression> query)
+        -> outcome_v2::std_result<std::shared_ptr<Expression>> {
+    if (nullptr == query) {
+        return ErrorCode{ErrorCodeEnum::QueryExpressionIsNull};
+    }
+
+    if (nullptr != std::dynamic_pointer_cast<EmptyExpr>(query)) {
+        return query;
+    }
+
+    try {
+        if (query = clp_s::search::ast::OrOfAndForm{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        if (query = clp_s::search::ast::NarrowTypes{}.run(query);
+            nullptr != std::dynamic_pointer_cast<EmptyExpr>(query))
+        {
+            return query;
+        }
+
+        return clp_s::search::ast::ConvertToExists{}.run(query);
+    } catch (std::exception const& ex) {
+        return ErrorCode{ErrorCodeEnum::QueryTransformationPassFailed};
+    }
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp
user: login: coderabbitai[bot]
body: @LinZhihao-723 Understood, I'll respect your decision not to add logging in this catch block. Thanks for the clarification.


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@LinZhihao-723 Understood, I'll respect your decision not to add logging in this catch block. Thanks for the clarification.</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-04T21:09:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,350 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param colum_descriptor
+         * @return A result containing the constructed token iterator on success, or an error code
+         * indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the column doesn't have any
+         *   token.
+         */
+        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* colum_descriptor)
+                -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            auto const token_begin_it{colum_descriptor->descriptor_begin()};
+            if (colum_descriptor->descriptor_end() == token_begin_it) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{colum_descriptor, token_begin_it};
+        }
+
+        // Methods
+        /**
+         * @return Whether this is the last token of the underlying column descriptor.
+         */
+        [[nodiscard]] auto is_last() const -> bool {
+            return m_column_descriptor->descriptor_end() == m_next_token_it;
+        }
+
+        /**
+         * @return A result containing a newly constructor iterator pointing to the next token on
+         * success, or an error code indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBound if the current token is already
+         *   the last one.
+         */
+        [[nodiscard]] auto next() const -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            if (is_last()) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{m_column_descriptor, m_next_token_it};
+        }
+
+        [[nodiscard]] auto is_wildcard() const -> bool { return m_curr_token_it->wildcard(); }
+
+        [[nodiscard]] auto get_token() const -> std::string_view {
+            return m_curr_token_it->get_token();
+        }
+
+        [[nodiscard]] auto get_column_descriptor() const -> clp_s::search::ast::ColumnDescriptor* {
+            return m_column_descriptor;
+        }
+
+        /**
+         * @param type
+         * @return Whether the underlying column can match the given schema-tree node type.
+         */
+        [[nodiscard]] auto match(SchemaTree::Node::Type type) const -> bool {
+            return m_column_descriptor->matches_any(schema_tree_node_type_to_literal_types(type));
+        }
+
+    private:
+        // Constructor
+        ColumnDescriptorTokenIterator(
+                clp_s::search::ast::ColumnDescriptor* colum_descriptor,
+                clp_s::search::ast::DescriptorList::iterator curr_token_it
+        )
+                : m_column_descriptor{colum_descriptor},
+                  m_curr_token_it{curr_token_it},
+                  m_next_token_it{curr_token_it + 1} {}
+
+        clp_s::search::ast::ColumnDescriptor* m_column_descriptor;
+        clp_s::search::ast::DescriptorList::iterator m_curr_token_it;
+        clp_s::search::ast::DescriptorList::iterator m_next_token_it;
+    };
+
+    using ProjectionMap = std::unordered_map<clp_s::search::ast::ColumnDescriptor*, std::string>;
+
+    using PartialResolutionMap = std::
+            unordered_map<SchemaTree::Node::id_t, std::vector<ColumnDescriptorTokenIterator>>;
+
+    // Factory function
+    /**
+     * @param query The search query.
+     * @param projections The columns to project.
+     * @param case_sensitive_match Whether to use case-sensitive match for string comparison.
+     * @return A result containing the newly constructed `QueryHandler` on success, or an error code
+     * indicating the failure:
+     * - Forwards `preprocess_query`'s return values.
+     * - Forwards `create_projected_columns_and_projection_map`'s return values.
+     * - Forwards `create_initial_partial_resolutions`'s return values.
+     */
+    [[nodiscard]] static auto create(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            std::vector<std::pair<std::string, clp_s::search::ast::LiteralTypeBitmask>> const&
+                    projections,
+            bool case_sensitive_match
+    ) -> outcome_v2::std_result<QueryHandlerImpl>;
+
+    // Delete copy constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl const&) = delete;
+    auto operator=(QueryHandlerImpl const&) -> QueryHandlerImpl& = delete;
+
+    // Default move constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl&&) = default;
+    auto operator=(QueryHandlerImpl&&) -> QueryHandlerImpl& = default;
+
+    // Destructor
+    ~QueryHandlerImpl() = default;
+
+    /**
+     * Implementation of `QueryHandler::evaluate_node_id_value_pairs`.
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - TODO
+     */
+    [[nodiscard]] auto evaluate_node_id_value_pairs(
+            KeyValuePairLogEvent::NodeIdValuePairs const& auto_gen_node_id_value_pairs,
+            KeyValuePairLogEvent::NodeIdValuePairs const& user_gen_node_id_value_pairs
+    ) -> outcome_v2::std_result<AstEvaluationResult>;
+
+    /**
+     * Implementation of `QueryHandler::update_partially_resolved_columns` with new projected
+     * schema-tree node callback given as a template parameter.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @param new_projected_schema_tree_node_callback
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `handle_column_resolution_on_leaf_node`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto update_partially_resolved_columns(
+            bool is_auto_generated,
+            SchemaTree::NodeLocator const& node_locator,
+            SchemaTree::Node::id_t node_id,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    [[nodiscard]] auto get_resolved_column_to_schema_tree_node_ids() const -> std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>> {
+        return m_resolved_column_to_schema_tree_node_ids;
+    }
+
+private:
+    // Constructor
+    QueryHandlerImpl(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            PartialResolutionMap auto_gen_namespace_partial_resolutions,
+            PartialResolutionMap user_gen_namespace_partial_resolutions,
+            std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> projected_columns,
+            ProjectionMap projected_column_to_original_key,
+            bool case_sensitive_match
+    )
+            : m_query{std::move(query)},
+              m_auto_gen_namespace_partial_resolutions{
+                      std::move(auto_gen_namespace_partial_resolutions)
+              },
+              m_user_gen_namespace_partial_resolutions{
+                      std::move(user_gen_namespace_partial_resolutions)
+              },
+              m_projected_columns{std::move(projected_columns)},
+              m_projected_column_to_original_key{std::move(projected_column_to_original_key)},
+              m_case_sensitive_search{case_sensitive_match} {}
+
+    // Methods
+    /**
+     * Handles column resolution on the given schema-tree leaf node and the token in the column.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param is_auto_generated
+     * @param leaf_node_id
+     * @param leaf_node_locator
+     * @param token_it The current resolved token in the column.
+     * @param new_projected_schema_tree_node_callback
+     * @return A void result on success, or an error code indicating the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `new_projected_schema_tree_node_callback`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto handle_column_resolution_on_leaf_node(
+            bool is_auto_generated,
+            SchemaTree::Node::id_t leaf_node_id,
+            SchemaTree::NodeLocator const& leaf_node_locator,
+            ColumnDescriptorTokenIterator const& token_it,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    // Variables
+    std::shared_ptr<clp_s::search::ast::Expression> m_query;
+    PartialResolutionMap m_auto_gen_namespace_partial_resolutions;
+    PartialResolutionMap m_user_gen_namespace_partial_resolutions;
+    std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>>
+            m_resolved_column_to_schema_tree_node_ids;
+    std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> m_projected_columns;
+    ProjectionMap m_projected_column_to_original_key;
+    bool m_case_sensitive_search;
+};
+
+template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+auto QueryHandlerImpl::update_partially_resolved_columns(
+        bool is_auto_generated,
+        SchemaTree::NodeLocator const& node_locator,
+        SchemaTree::Node::id_t node_id,
+        NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+) -> outcome_v2::std_result<void> {
+    auto const parent_node_id{node_locator.get_parent_id()};
+    auto& partial_resolutions_to_update{
+            is_auto_generated ? m_auto_gen_namespace_partial_resolutions
+                              : m_user_gen_namespace_partial_resolutions
+    };
+    if (false == partial_resolutions_to_update.contains(parent_node_id)) {
+        return outcome_v2::success();
+    }
+
+    std::vector<std::pair<SchemaTree::Node::id_t, ColumnDescriptorTokenIterator>>
+            new_partial_resolutions;
+    for (auto const& token_it : partial_resolutions_to_update.at(parent_node_id)) {
+        if (SchemaTree::Node::Type::Obj == node_locator.get_type()) {
+            // Handle schema-tree non-leaf nodes.
+            if (token_it.is_last() && false == token_it.is_wildcard()) {
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: LinZhihao-723
body: Rewrite `update_partially_resolved_columns`'s implementation to:
- Solve the problem in this review comments.
- Improve the overall readability with more comments.
created_at: 2025-05-05T00:21:45Z
author_association: MEMBER
diff_hunk: @@ -25,6 +33,14 @@ auto ErrorCategory::message(ErrorCodeEnum error_enum) const -> std::string {
             return "Unsupported literal type.";
         case ErrorCodeEnum::MethodNotImplemented:
             return "The requested method is not implemented.";
+        case ErrorCodeEnum::ProjectionColumnDescriptorCreationFailure:
+            return "Failed to create a column descriptor for the given projection.";
+        case ErrorCodeEnum::QueryExpressionIsNull:
+            return "The query expression is NULL.";
+        case ErrorCodeEnum::QueryTransformationPassFailed:
+            return "Failed to execute transformation passes on the query expression.";
+        case ErrorCodeEnum::UnsupportedNamespace:
path: components/core/src/clp/ffi/ir_stream/search/ErrorCode.cpp
user: login: LinZhihao-723
body: Resolved by ignoring unrecognized namespaces.
Also added test cases to cover columns with unrecognized namespaces.
created_at: 2025-05-05T00:22:57Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,153 @@
+#include "utils.hpp"
+
+#include <bitset>
+#include <cstddef>
+#include <map>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <fmt/core.h>
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../SchemaTree.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::search::ast::LiteralType;
+}  // namespace
+
+auto ColumnQueryPossibleMatches::get_matchable_node_ids_from_literal_type(LiteralType type) const
+        -> std::vector<SchemaTree::Node::id_t> {
+    if ((type & m_matchable_types) == 0) {
+        return {};
+    }
+
+    switch (type) {
+        case LiteralType::IntegerT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Int);
+        case LiteralType::FloatT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Float);
+        case LiteralType::BooleanT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Bool);
+        case LiteralType::VarStringT:
+        case LiteralType::ClpStringT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Str);
+        default:
+            return {};
+    }
+}
+
+auto ColumnQueryPossibleMatches::get_matchable_node_ids_from_schema_tree_type(
+        SchemaTree::Node::Type type
+) const -> std::vector<SchemaTree::Node::id_t> {
+    std::vector<SchemaTree::Node::id_t> node_ids;
+    for (auto const node_id : m_matchable_node_ids) {
+        if (m_schema_tree->get_node(node_id).get_type() == type) {
+            node_ids.emplace_back(node_id);
+        }
+    }
+    return node_ids;
+}
+
+auto ColumnQueryPossibleMatches::serialize() const -> std::string {
+    constexpr size_t cBitsetSize{sizeof(m_matchable_types) * 8};
+    return fmt::format(
+            "Types: {}; Node IDs: {{{}}}",
+            std::bitset<cBitsetSize>(m_matchable_types).to_string(),
+            fmt::join(m_matchable_node_ids, ",")
+    );
+}
+
+auto trivial_new_projected_schema_tree_node_callback(
+        [[maybe_unused]] bool is_auto_generated,
+        [[maybe_unused]] SchemaTree::Node::id_t node_id,
+        [[maybe_unused]] std::string_view projected_key_path
+) -> outcome_v2::std_result<void> {
+    return outcome_v2::success();
+}
+
+auto get_schema_tree_column_queries(std::shared_ptr<SchemaTree> const& schema_tree)
+        -> std::map<std::string, ColumnQueryPossibleMatches> {
+    std::map<std::string, ColumnQueryPossibleMatches> column_query_to_possible_matches;
+    std::vector<std::pair<std::string, SchemaTree::Node::id_t>> search_stack;
+
+    auto push_all_children_to_stack
+            = [&](SchemaTree::Node const& node, std::string const& query) -> void {
+        for (auto const node_id : node.get_children_ids()) {
+            search_stack.emplace_back(query, node_id);
+        }
+    };
path: components/core/src/clp/ffi/ir_stream/search/test/utils.cpp
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Minor efficiency tweak – avoid repeated string copies inside the DFS helper**  

`push_all_children_to_stack` captures `query` by const-ref but then copies it for every child.  
Because the function is called for every node, the cumulative copying cost can be noticeable for
large schema-trees.  Consider moving the construction of the child query string outside of the
lambda, or emplacing `std::string_view` into `search_stack` if the lifetime permits.

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-05T00:25:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,352 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param column_descriptor
+         * @return A result containing the constructed token iterator on success, or an error code
+         * indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds if the column doesn't have any
+         *   tokens.
+         */
+        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* column_descriptor)
+                -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            auto const token_begin_it{column_descriptor->descriptor_begin()};
+            if (column_descriptor->descriptor_end() == token_begin_it) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{column_descriptor, token_begin_it};
+        }
+
+        // Methods
+        /**
+         * @return Whether this is the last token of the underlying column descriptor.
+         */
+        [[nodiscard]] auto is_last() const -> bool {
+            return m_column_descriptor->descriptor_end() == m_next_token_it;
+        }
+
+        /**
+         * @return A result containing a newly constructor iterator pointing to the next token on
+         * success, or an error code indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds if the current token is already
+         *   the last one.
+         */
+        [[nodiscard]] auto next() const -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            if (is_last()) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{m_column_descriptor, m_next_token_it};
+        }
+
+        [[nodiscard]] auto is_wildcard() const -> bool { return m_curr_token_it->wildcard(); }
+
+        [[nodiscard]] auto is_trailing_wildcard() const -> bool {
+            return is_last() && is_wildcard();
+        }
+
+        [[nodiscard]] auto get_token() const -> std::string_view {
+            return m_curr_token_it->get_token();
+        }
+
+        [[nodiscard]] auto get_column_descriptor() const -> clp_s::search::ast::ColumnDescriptor* {
+            return m_column_descriptor;
+        }
+
+        /**
+         * @param type
+         * @return Whether the underlying column can match the given schema-tree node type.
+         */
+        [[nodiscard]] auto match_schema_tree_node_type(SchemaTree::Node::Type type) const -> bool {
+            return m_column_descriptor->matches_any(schema_tree_node_type_to_literal_types(type));
+        }
+
+    private:
+        // Constructor
+        ColumnDescriptorTokenIterator(
+                clp_s::search::ast::ColumnDescriptor* column_descriptor,
+                clp_s::search::ast::DescriptorList::iterator curr_token_it
+        )
+                : m_column_descriptor{column_descriptor},
+                  m_curr_token_it{curr_token_it},
+                  m_next_token_it{curr_token_it + 1} {}
+
+        clp_s::search::ast::ColumnDescriptor* m_column_descriptor;
+        clp_s::search::ast::DescriptorList::iterator m_curr_token_it;
+        clp_s::search::ast::DescriptorList::iterator m_next_token_it;
+    };
+
+    using ProjectionMap = std::unordered_map<clp_s::search::ast::ColumnDescriptor*, std::string>;
+
+    using PartialResolutionMap = std::
+            unordered_map<SchemaTree::Node::id_t, std::vector<ColumnDescriptorTokenIterator>>;
+
+    // Factory function
+    /**
+     * @param query The search query.
+     * @param projections The columns to project.
+     * @param case_sensitive_match Whether to use case-sensitive match for string comparison.
+     * @return A result containing the newly constructed `QueryHandler` on success, or an error code
+     * indicating the failure:
+     * - Forwards `preprocess_query`'s return values.
+     * - Forwards `create_projected_columns_and_projection_map`'s return values.
+     * - Forwards `create_initial_partial_resolutions`'s return values.
+     */
+    [[nodiscard]] static auto create(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            std::vector<std::pair<std::string, clp_s::search::ast::LiteralTypeBitmask>> const&
+                    projections,
+            bool case_sensitive_match
+    ) -> outcome_v2::std_result<QueryHandlerImpl>;
+
+    // Delete copy constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl const&) = delete;
+    auto operator=(QueryHandlerImpl const&) -> QueryHandlerImpl& = delete;
+
+    // Default move constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl&&) = default;
+    auto operator=(QueryHandlerImpl&&) -> QueryHandlerImpl& = default;
+
+    // Destructor
+    ~QueryHandlerImpl() = default;
+
+    /**
+     * Implementation of `QueryHandler::evaluate_node_id_value_pairs`.
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - TODO
+     */
+    [[nodiscard]] auto evaluate_node_id_value_pairs(
+            KeyValuePairLogEvent::NodeIdValuePairs const& auto_gen_node_id_value_pairs,
+            KeyValuePairLogEvent::NodeIdValuePairs const& user_gen_node_id_value_pairs
+    ) -> outcome_v2::std_result<AstEvaluationResult>;
+
+    /**
+     * Implementation of `QueryHandler::update_partially_resolved_columns` with new projected
+     * schema-tree node callback given as a template parameter.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @param new_projected_schema_tree_node_callback
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `handle_column_resolution_on_new_schema_tree_node`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto update_partially_resolved_columns(
+            bool is_auto_generated,
+            SchemaTree::NodeLocator const& node_locator,
+            SchemaTree::Node::id_t node_id,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    [[nodiscard]] auto get_resolved_column_to_schema_tree_node_ids() const -> std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>> {
+        return m_resolved_column_to_schema_tree_node_ids;
+    }
+
+private:
+    // Constructor
+    QueryHandlerImpl(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            PartialResolutionMap auto_gen_namespace_partial_resolutions,
+            PartialResolutionMap user_gen_namespace_partial_resolutions,
+            std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> projected_columns,
+            ProjectionMap projected_column_to_original_key,
+            bool case_sensitive_match
+    )
+            : m_query{std::move(query)},
+              m_auto_gen_namespace_partial_resolutions{
+                      std::move(auto_gen_namespace_partial_resolutions)
+              },
+              m_user_gen_namespace_partial_resolutions{
+                      std::move(user_gen_namespace_partial_resolutions)
+              },
+              m_projected_columns{std::move(projected_columns)},
+              m_projected_column_to_original_key{std::move(projected_column_to_original_key)},
+              m_case_sensitive_search{case_sensitive_match} {}
+
+    // Methods
+    /**
+     * Handles column resolution of the given token iterator on the a new schema-tree tree node.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param is_auto_generated
+     * @param node_id
+     * @param node_locator
+     * @param token_it The current resolved token in the column.
+     * @param new_projected_schema_tree_node_callback
+     * @return A void result on success, or an error code indicating the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `new_projected_schema_tree_node_callback`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto handle_column_resolution_on_new_schema_tree_node(
+            bool is_auto_generated,
+            SchemaTree::Node::id_t node_id,
+            SchemaTree::NodeLocator const& node_locator,
+            ColumnDescriptorTokenIterator const& token_it,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    // Variables
+    std::shared_ptr<clp_s::search::ast::Expression> m_query;
+    PartialResolutionMap m_auto_gen_namespace_partial_resolutions;
+    PartialResolutionMap m_user_gen_namespace_partial_resolutions;
+    std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>>
+            m_resolved_column_to_schema_tree_node_ids;
+    std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> m_projected_columns;
+    ProjectionMap m_projected_column_to_original_key;
+    bool m_case_sensitive_search;
+};
+
+template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+auto QueryHandlerImpl::update_partially_resolved_columns(
+        bool is_auto_generated,
+        SchemaTree::NodeLocator const& node_locator,
+        SchemaTree::Node::id_t node_id,
+        NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+) -> outcome_v2::std_result<void> {
+    auto const parent_node_id{node_locator.get_parent_id()};
+    auto& partial_resolutions_to_update{
+            is_auto_generated ? m_auto_gen_namespace_partial_resolutions
+                              : m_user_gen_namespace_partial_resolutions
+    };
+    if (false == partial_resolutions_to_update.contains(parent_node_id)) {
+        return outcome_v2::success();
+    }
+
+    std::vector<std::pair<SchemaTree::Node::id_t, ColumnDescriptorTokenIterator>>
+            new_partial_resolutions;
+    for (auto const& token_it : partial_resolutions_to_update.at(parent_node_id)) {
+        OUTCOME_TRYV(handle_column_resolution_on_new_schema_tree_node(
+                is_auto_generated,
+                node_id,
+                node_locator,
+                token_it,
+                new_projected_schema_tree_node_callback
+        ));
+
+        // Handle partial resolution updates
+        if (SchemaTree::Node::Type::Obj != node_locator.get_type()) {
+            // If the schema-tree node is not of type `Obj`, it cannot have children, so no further
+            // resolution is possible from this node.
+            continue;
+        }
+
+        if (token_it.is_wildcard()) {
+            // For wildcard tokens, simulate potential matches by:
+            // - Adding a resolution that assumes this node matches part of the wildcard.
+            // - If this isn't the last wildcard token, also add a resolution for continuing the
+            //   match.
+            new_partial_resolutions.emplace_back(node_id, token_it);
+            if (false == token_it.is_last()) {
+                new_partial_resolutions.emplace_back(node_id, OUTCOME_TRYX(token_it.next()));
+            }
+            continue;
+        }
+
+        if (token_it.is_last() || token_it.get_token() != node_locator.get_key_name()) {
+            // If this is the last token, or the token doesn't match the node's key, then no further
+            // resolution is possible through the given node.
+            continue;
+        }
+
+        auto const next_token_it{OUTCOME_TRYX(token_it.next())};
+        new_partial_resolutions.emplace_back(node_id, next_token_it);
+        if (false == next_token_it.is_last() && next_token_it.is_wildcard()) {
+            // Handle the case where the wildcard matches nothing
+            new_partial_resolutions.emplace_back(node_id, OUTCOME_TRYX(next_token_it.next()));
+        }
+    }
+
+    for (auto const [node_id, token_it] : new_partial_resolutions) {
+        auto [it, inserted] = partial_resolutions_to_update.try_emplace(
+                node_id,
+                std::vector<ColumnDescriptorTokenIterator>{}
+        );
+        it->second.emplace_back(token_it);
+    }
+
+    return outcome_v2::success();
+}
+
+template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+auto QueryHandlerImpl::handle_column_resolution_on_new_schema_tree_node(
+        bool is_auto_generated,
+        SchemaTree::Node::id_t node_id,
+        SchemaTree::NodeLocator const& node_locator,
+        QueryHandlerImpl::ColumnDescriptorTokenIterator const& token_it,
+        NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+) -> outcome_v2::std_result<void> {
+    if ((false == token_it.is_last()
+         && false == OUTCOME_TRYX(token_it.next()).is_trailing_wildcard())
+        || false == token_it.match_schema_tree_node_type(node_locator.get_type())
+        || (false == token_it.is_wildcard() && token_it.get_token() != node_locator.get_key_name()))
+    {
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Case-sensitivity bug repeats in `handle_column_resolution_on_new_schema_tree_node`**  

The same strict comparison is used here, so the fix above should be applied as well.

```diff
-        || (false == token_it.is_wildcard() && token_it.get_token() != node_locator.get_key_name()))
+        || (false == token_it.is_wildcard()
+            && (m_case_sensitive_search
+                ? token_it.get_token() != node_locator.get_key_name()
+                : false == utils::iequals(token_it.get_token(), node_locator.get_key_name()))))
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    if ((false == token_it.is_last()
         && false == OUTCOME_TRYX(token_it.next()).is_trailing_wildcard())
        || false == token_it.match_schema_tree_node_type(node_locator.get_type())
        || (false == token_it.is_wildcard()
            && (m_case_sensitive_search
                ? token_it.get_token() != node_locator.get_key_name()
                : false == utils::iequals(token_it.get_token(), node_locator.get_key_name()))))
    {
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-05T00:25:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,153 @@
+#include "utils.hpp"
+
+#include <bitset>
+#include <cstddef>
+#include <map>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <fmt/core.h>
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../SchemaTree.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::search::ast::LiteralType;
+}  // namespace
+
+auto ColumnQueryPossibleMatches::get_matchable_node_ids_from_literal_type(LiteralType type) const
+        -> std::vector<SchemaTree::Node::id_t> {
+    if ((type & m_matchable_types) == 0) {
+        return {};
+    }
+
+    switch (type) {
+        case LiteralType::IntegerT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Int);
+        case LiteralType::FloatT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Float);
+        case LiteralType::BooleanT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Bool);
+        case LiteralType::VarStringT:
+        case LiteralType::ClpStringT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Str);
+        default:
+            return {};
+    }
+}
+
+auto ColumnQueryPossibleMatches::get_matchable_node_ids_from_schema_tree_type(
+        SchemaTree::Node::Type type
+) const -> std::vector<SchemaTree::Node::id_t> {
+    std::vector<SchemaTree::Node::id_t> node_ids;
+    for (auto const node_id : m_matchable_node_ids) {
+        if (m_schema_tree->get_node(node_id).get_type() == type) {
+            node_ids.emplace_back(node_id);
+        }
+    }
+    return node_ids;
+}
+
+auto ColumnQueryPossibleMatches::serialize() const -> std::string {
+    constexpr size_t cBitsetSize{sizeof(m_matchable_types) * 8};
+    return fmt::format(
+            "Types: {}; Node IDs: {{{}}}",
+            std::bitset<cBitsetSize>(m_matchable_types).to_string(),
+            fmt::join(m_matchable_node_ids, ",")
+    );
+}
+
+auto trivial_new_projected_schema_tree_node_callback(
+        [[maybe_unused]] bool is_auto_generated,
+        [[maybe_unused]] SchemaTree::Node::id_t node_id,
+        [[maybe_unused]] std::string_view projected_key_path
+) -> outcome_v2::std_result<void> {
+    return outcome_v2::success();
+}
+
+auto get_schema_tree_column_queries(std::shared_ptr<SchemaTree> const& schema_tree)
+        -> std::map<std::string, ColumnQueryPossibleMatches> {
+    std::map<std::string, ColumnQueryPossibleMatches> column_query_to_possible_matches;
+    std::vector<std::pair<std::string, SchemaTree::Node::id_t>> search_stack;
+
+    auto push_all_children_to_stack
+            = [&](SchemaTree::Node const& node, std::string const& query) -> void {
+        for (auto const node_id : node.get_children_ids()) {
+            search_stack.emplace_back(query, node_id);
+        }
+    };
path: components/core/src/clp/ffi/ir_stream/search/test/utils.cpp
user: login: LinZhihao-723
body: - `emplace_back` will do an emplace construction
- `std::string_view` shouldn't be allowed since the stack node should own this string (otherwise there's no owner)
- This part of code is used for testing only; readability and simplicity is preferred rather than the performaced.
created_at: 2025-05-05T01:10:13Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,153 @@
+#include "utils.hpp"
+
+#include <bitset>
+#include <cstddef>
+#include <map>
+#include <memory>
+#include <ostream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <fmt/core.h>
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../SchemaTree.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::search::ast::LiteralType;
+}  // namespace
+
+auto ColumnQueryPossibleMatches::get_matchable_node_ids_from_literal_type(LiteralType type) const
+        -> std::vector<SchemaTree::Node::id_t> {
+    if ((type & m_matchable_types) == 0) {
+        return {};
+    }
+
+    switch (type) {
+        case LiteralType::IntegerT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Int);
+        case LiteralType::FloatT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Float);
+        case LiteralType::BooleanT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Bool);
+        case LiteralType::VarStringT:
+        case LiteralType::ClpStringT:
+            return get_matchable_node_ids_from_schema_tree_type(SchemaTree::Node::Type::Str);
+        default:
+            return {};
+    }
+}
+
+auto ColumnQueryPossibleMatches::get_matchable_node_ids_from_schema_tree_type(
+        SchemaTree::Node::Type type
+) const -> std::vector<SchemaTree::Node::id_t> {
+    std::vector<SchemaTree::Node::id_t> node_ids;
+    for (auto const node_id : m_matchable_node_ids) {
+        if (m_schema_tree->get_node(node_id).get_type() == type) {
+            node_ids.emplace_back(node_id);
+        }
+    }
+    return node_ids;
+}
+
+auto ColumnQueryPossibleMatches::serialize() const -> std::string {
+    constexpr size_t cBitsetSize{sizeof(m_matchable_types) * 8};
+    return fmt::format(
+            "Types: {}; Node IDs: {{{}}}",
+            std::bitset<cBitsetSize>(m_matchable_types).to_string(),
+            fmt::join(m_matchable_node_ids, ",")
+    );
+}
+
+auto trivial_new_projected_schema_tree_node_callback(
+        [[maybe_unused]] bool is_auto_generated,
+        [[maybe_unused]] SchemaTree::Node::id_t node_id,
+        [[maybe_unused]] std::string_view projected_key_path
+) -> outcome_v2::std_result<void> {
+    return outcome_v2::success();
+}
+
+auto get_schema_tree_column_queries(std::shared_ptr<SchemaTree> const& schema_tree)
+        -> std::map<std::string, ColumnQueryPossibleMatches> {
+    std::map<std::string, ColumnQueryPossibleMatches> column_query_to_possible_matches;
+    std::vector<std::pair<std::string, SchemaTree::Node::id_t>> search_stack;
+
+    auto push_all_children_to_stack
+            = [&](SchemaTree::Node const& node, std::string const& query) -> void {
+        for (auto const node_id : node.get_children_ids()) {
+            search_stack.emplace_back(query, node_id);
+        }
+    };
path: components/core/src/clp/ffi/ir_stream/search/test/utils.cpp
user: login: coderabbitai[bot]
body: @LinZhihao-723 I understand your reasoning and agree with your decision. You're right that:

1. Even though emplace_back avoids one level of copying, the string itself still gets copied
2. Using string_view would introduce ownership issues
3. For test code, readability and simplicity are often better priorities than performance

Thanks for the thoughtful explanation. Your approach makes sense for this context.


<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@LinZhihao-723 I understand your reasoning and agree with your decision. You're right that:

1. Even though emplace_back avoids one level of copying, the string itself still gets copied
2. Using string_view would introduce ownership issues
3. For test code, readability and simplicity are often better priorities than performance

Thanks for the thoughtful explanation. Your approach makes sense for this context.</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-05T01:10:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,352 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param column_descriptor
+         * @return A result containing the constructed token iterator on success, or an error code
+         * indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds if the column doesn't have any
+         *   tokens.
+         */
+        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* column_descriptor)
+                -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            auto const token_begin_it{column_descriptor->descriptor_begin()};
+            if (column_descriptor->descriptor_end() == token_begin_it) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{column_descriptor, token_begin_it};
+        }
+
+        // Methods
+        /**
+         * @return Whether this is the last token of the underlying column descriptor.
+         */
+        [[nodiscard]] auto is_last() const -> bool {
+            return m_column_descriptor->descriptor_end() == m_next_token_it;
+        }
+
+        /**
+         * @return A result containing a newly constructor iterator pointing to the next token on
+         * success, or an error code indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds if the current token is already
+         *   the last one.
+         */
+        [[nodiscard]] auto next() const -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            if (is_last()) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{m_column_descriptor, m_next_token_it};
+        }
+
+        [[nodiscard]] auto is_wildcard() const -> bool { return m_curr_token_it->wildcard(); }
+
+        [[nodiscard]] auto is_trailing_wildcard() const -> bool {
+            return is_last() && is_wildcard();
+        }
+
+        [[nodiscard]] auto get_token() const -> std::string_view {
+            return m_curr_token_it->get_token();
+        }
+
+        [[nodiscard]] auto get_column_descriptor() const -> clp_s::search::ast::ColumnDescriptor* {
+            return m_column_descriptor;
+        }
+
+        /**
+         * @param type
+         * @return Whether the underlying column can match the given schema-tree node type.
+         */
+        [[nodiscard]] auto match_schema_tree_node_type(SchemaTree::Node::Type type) const -> bool {
+            return m_column_descriptor->matches_any(schema_tree_node_type_to_literal_types(type));
+        }
+
+    private:
+        // Constructor
+        ColumnDescriptorTokenIterator(
+                clp_s::search::ast::ColumnDescriptor* column_descriptor,
+                clp_s::search::ast::DescriptorList::iterator curr_token_it
+        )
+                : m_column_descriptor{column_descriptor},
+                  m_curr_token_it{curr_token_it},
+                  m_next_token_it{curr_token_it + 1} {}
+
+        clp_s::search::ast::ColumnDescriptor* m_column_descriptor;
+        clp_s::search::ast::DescriptorList::iterator m_curr_token_it;
+        clp_s::search::ast::DescriptorList::iterator m_next_token_it;
+    };
+
+    using ProjectionMap = std::unordered_map<clp_s::search::ast::ColumnDescriptor*, std::string>;
+
+    using PartialResolutionMap = std::
+            unordered_map<SchemaTree::Node::id_t, std::vector<ColumnDescriptorTokenIterator>>;
+
+    // Factory function
+    /**
+     * @param query The search query.
+     * @param projections The columns to project.
+     * @param case_sensitive_match Whether to use case-sensitive match for string comparison.
+     * @return A result containing the newly constructed `QueryHandler` on success, or an error code
+     * indicating the failure:
+     * - Forwards `preprocess_query`'s return values.
+     * - Forwards `create_projected_columns_and_projection_map`'s return values.
+     * - Forwards `create_initial_partial_resolutions`'s return values.
+     */
+    [[nodiscard]] static auto create(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            std::vector<std::pair<std::string, clp_s::search::ast::LiteralTypeBitmask>> const&
+                    projections,
+            bool case_sensitive_match
+    ) -> outcome_v2::std_result<QueryHandlerImpl>;
+
+    // Delete copy constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl const&) = delete;
+    auto operator=(QueryHandlerImpl const&) -> QueryHandlerImpl& = delete;
+
+    // Default move constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl&&) = default;
+    auto operator=(QueryHandlerImpl&&) -> QueryHandlerImpl& = default;
+
+    // Destructor
+    ~QueryHandlerImpl() = default;
+
+    /**
+     * Implementation of `QueryHandler::evaluate_node_id_value_pairs`.
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - TODO
+     */
+    [[nodiscard]] auto evaluate_node_id_value_pairs(
+            KeyValuePairLogEvent::NodeIdValuePairs const& auto_gen_node_id_value_pairs,
+            KeyValuePairLogEvent::NodeIdValuePairs const& user_gen_node_id_value_pairs
+    ) -> outcome_v2::std_result<AstEvaluationResult>;
+
+    /**
+     * Implementation of `QueryHandler::update_partially_resolved_columns` with new projected
+     * schema-tree node callback given as a template parameter.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @param new_projected_schema_tree_node_callback
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `handle_column_resolution_on_new_schema_tree_node`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto update_partially_resolved_columns(
+            bool is_auto_generated,
+            SchemaTree::NodeLocator const& node_locator,
+            SchemaTree::Node::id_t node_id,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    [[nodiscard]] auto get_resolved_column_to_schema_tree_node_ids() const -> std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>> {
+        return m_resolved_column_to_schema_tree_node_ids;
+    }
+
+private:
+    // Constructor
+    QueryHandlerImpl(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            PartialResolutionMap auto_gen_namespace_partial_resolutions,
+            PartialResolutionMap user_gen_namespace_partial_resolutions,
+            std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> projected_columns,
+            ProjectionMap projected_column_to_original_key,
+            bool case_sensitive_match
+    )
+            : m_query{std::move(query)},
+              m_auto_gen_namespace_partial_resolutions{
+                      std::move(auto_gen_namespace_partial_resolutions)
+              },
+              m_user_gen_namespace_partial_resolutions{
+                      std::move(user_gen_namespace_partial_resolutions)
+              },
+              m_projected_columns{std::move(projected_columns)},
+              m_projected_column_to_original_key{std::move(projected_column_to_original_key)},
+              m_case_sensitive_search{case_sensitive_match} {}
+
+    // Methods
+    /**
+     * Handles column resolution of the given token iterator on the a new schema-tree tree node.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param is_auto_generated
+     * @param node_id
+     * @param node_locator
+     * @param token_it The current resolved token in the column.
+     * @param new_projected_schema_tree_node_callback
+     * @return A void result on success, or an error code indicating the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `new_projected_schema_tree_node_callback`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto handle_column_resolution_on_new_schema_tree_node(
+            bool is_auto_generated,
+            SchemaTree::Node::id_t node_id,
+            SchemaTree::NodeLocator const& node_locator,
+            ColumnDescriptorTokenIterator const& token_it,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    // Variables
+    std::shared_ptr<clp_s::search::ast::Expression> m_query;
+    PartialResolutionMap m_auto_gen_namespace_partial_resolutions;
+    PartialResolutionMap m_user_gen_namespace_partial_resolutions;
+    std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>>
+            m_resolved_column_to_schema_tree_node_ids;
+    std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> m_projected_columns;
+    ProjectionMap m_projected_column_to_original_key;
+    bool m_case_sensitive_search;
+};
+
+template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+auto QueryHandlerImpl::update_partially_resolved_columns(
+        bool is_auto_generated,
+        SchemaTree::NodeLocator const& node_locator,
+        SchemaTree::Node::id_t node_id,
+        NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+) -> outcome_v2::std_result<void> {
+    auto const parent_node_id{node_locator.get_parent_id()};
+    auto& partial_resolutions_to_update{
+            is_auto_generated ? m_auto_gen_namespace_partial_resolutions
+                              : m_user_gen_namespace_partial_resolutions
+    };
+    if (false == partial_resolutions_to_update.contains(parent_node_id)) {
+        return outcome_v2::success();
+    }
+
+    std::vector<std::pair<SchemaTree::Node::id_t, ColumnDescriptorTokenIterator>>
+            new_partial_resolutions;
+    for (auto const& token_it : partial_resolutions_to_update.at(parent_node_id)) {
+        OUTCOME_TRYV(handle_column_resolution_on_new_schema_tree_node(
+                is_auto_generated,
+                node_id,
+                node_locator,
+                token_it,
+                new_projected_schema_tree_node_callback
+        ));
+
+        // Handle partial resolution updates
+        if (SchemaTree::Node::Type::Obj != node_locator.get_type()) {
+            // If the schema-tree node is not of type `Obj`, it cannot have children, so no further
+            // resolution is possible from this node.
+            continue;
+        }
+
+        if (token_it.is_wildcard()) {
+            // For wildcard tokens, simulate potential matches by:
+            // - Adding a resolution that assumes this node matches part of the wildcard.
+            // - If this isn't the last wildcard token, also add a resolution for continuing the
+            //   match.
+            new_partial_resolutions.emplace_back(node_id, token_it);
+            if (false == token_it.is_last()) {
+                new_partial_resolutions.emplace_back(node_id, OUTCOME_TRYX(token_it.next()));
+            }
+            continue;
+        }
+
+        if (token_it.is_last() || token_it.get_token() != node_locator.get_key_name()) {
+            // If this is the last token, or the token doesn't match the node's key, then no further
+            // resolution is possible through the given node.
+            continue;
+        }
+
+        auto const next_token_it{OUTCOME_TRYX(token_it.next())};
+        new_partial_resolutions.emplace_back(node_id, next_token_it);
+        if (false == next_token_it.is_last() && next_token_it.is_wildcard()) {
+            // Handle the case where the wildcard matches nothing
+            new_partial_resolutions.emplace_back(node_id, OUTCOME_TRYX(next_token_it.next()));
+        }
+    }
+
+    for (auto const [node_id, token_it] : new_partial_resolutions) {
+        auto [it, inserted] = partial_resolutions_to_update.try_emplace(
+                node_id,
+                std::vector<ColumnDescriptorTokenIterator>{}
+        );
+        it->second.emplace_back(token_it);
+    }
+
+    return outcome_v2::success();
+}
+
+template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+auto QueryHandlerImpl::handle_column_resolution_on_new_schema_tree_node(
+        bool is_auto_generated,
+        SchemaTree::Node::id_t node_id,
+        SchemaTree::NodeLocator const& node_locator,
+        QueryHandlerImpl::ColumnDescriptorTokenIterator const& token_it,
+        NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+) -> outcome_v2::std_result<void> {
+    if ((false == token_it.is_last()
+         && false == OUTCOME_TRYX(token_it.next()).is_trailing_wildcard())
+        || false == token_it.match_schema_tree_node_type(node_locator.get_type())
+        || (false == token_it.is_wildcard() && token_it.get_token() != node_locator.get_key_name()))
+    {
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: LinZhihao-723
body: `m_case_sensitive_search` is used for actual string comparison but not the schema.
created_at: 2025-05-05T01:11:44Z
author_association: MEMBER
diff_hunk: @@ -0,0 +1,352 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_QUERYHANDLERIMPL_HPP
+
+#include <memory>
+#include <string>
+#include <string_view>
+#include <unordered_map>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../clp_s/search/ast/ColumnDescriptor.hpp"
+#include "../../../../clp_s/search/ast/Expression.hpp"
+#include "../../../../clp_s/search/ast/Literal.hpp"
+#include "../../KeyValuePairLogEvent.hpp"
+#include "../../SchemaTree.hpp"
+#include "AstEvaluationResult.hpp"
+#include "ErrorCode.hpp"
+#include "NewProjectedSchemaTreeNodeCallbackReq.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search {
+/**
+ * Implementation of `QueryHandler`.
+ */
+class QueryHandlerImpl {
+public:
+    // Types
+    /**
+     * Iterator of column descriptor tokens.
+     */
+    class ColumnDescriptorTokenIterator {
+    public:
+        // Factory function
+        /**
+         * Creates an iterator that points to the first token in the given column descriptor.
+         * @param column_descriptor
+         * @return A result containing the constructed token iterator on success, or an error code
+         * indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds if the column doesn't have any
+         *   tokens.
+         */
+        [[nodiscard]] static auto create(clp_s::search::ast::ColumnDescriptor* column_descriptor)
+                -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            auto const token_begin_it{column_descriptor->descriptor_begin()};
+            if (column_descriptor->descriptor_end() == token_begin_it) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{column_descriptor, token_begin_it};
+        }
+
+        // Methods
+        /**
+         * @return Whether this is the last token of the underlying column descriptor.
+         */
+        [[nodiscard]] auto is_last() const -> bool {
+            return m_column_descriptor->descriptor_end() == m_next_token_it;
+        }
+
+        /**
+         * @return A result containing a newly constructor iterator pointing to the next token on
+         * success, or an error code indicating the failure:
+         * - ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds if the current token is already
+         *   the last one.
+         */
+        [[nodiscard]] auto next() const -> outcome_v2::std_result<ColumnDescriptorTokenIterator> {
+            if (is_last()) {
+                return ErrorCode{ErrorCodeEnum::ColumnDescriptorTokenIteratorOutOfBounds};
+            }
+            return ColumnDescriptorTokenIterator{m_column_descriptor, m_next_token_it};
+        }
+
+        [[nodiscard]] auto is_wildcard() const -> bool { return m_curr_token_it->wildcard(); }
+
+        [[nodiscard]] auto is_trailing_wildcard() const -> bool {
+            return is_last() && is_wildcard();
+        }
+
+        [[nodiscard]] auto get_token() const -> std::string_view {
+            return m_curr_token_it->get_token();
+        }
+
+        [[nodiscard]] auto get_column_descriptor() const -> clp_s::search::ast::ColumnDescriptor* {
+            return m_column_descriptor;
+        }
+
+        /**
+         * @param type
+         * @return Whether the underlying column can match the given schema-tree node type.
+         */
+        [[nodiscard]] auto match_schema_tree_node_type(SchemaTree::Node::Type type) const -> bool {
+            return m_column_descriptor->matches_any(schema_tree_node_type_to_literal_types(type));
+        }
+
+    private:
+        // Constructor
+        ColumnDescriptorTokenIterator(
+                clp_s::search::ast::ColumnDescriptor* column_descriptor,
+                clp_s::search::ast::DescriptorList::iterator curr_token_it
+        )
+                : m_column_descriptor{column_descriptor},
+                  m_curr_token_it{curr_token_it},
+                  m_next_token_it{curr_token_it + 1} {}
+
+        clp_s::search::ast::ColumnDescriptor* m_column_descriptor;
+        clp_s::search::ast::DescriptorList::iterator m_curr_token_it;
+        clp_s::search::ast::DescriptorList::iterator m_next_token_it;
+    };
+
+    using ProjectionMap = std::unordered_map<clp_s::search::ast::ColumnDescriptor*, std::string>;
+
+    using PartialResolutionMap = std::
+            unordered_map<SchemaTree::Node::id_t, std::vector<ColumnDescriptorTokenIterator>>;
+
+    // Factory function
+    /**
+     * @param query The search query.
+     * @param projections The columns to project.
+     * @param case_sensitive_match Whether to use case-sensitive match for string comparison.
+     * @return A result containing the newly constructed `QueryHandler` on success, or an error code
+     * indicating the failure:
+     * - Forwards `preprocess_query`'s return values.
+     * - Forwards `create_projected_columns_and_projection_map`'s return values.
+     * - Forwards `create_initial_partial_resolutions`'s return values.
+     */
+    [[nodiscard]] static auto create(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            std::vector<std::pair<std::string, clp_s::search::ast::LiteralTypeBitmask>> const&
+                    projections,
+            bool case_sensitive_match
+    ) -> outcome_v2::std_result<QueryHandlerImpl>;
+
+    // Delete copy constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl const&) = delete;
+    auto operator=(QueryHandlerImpl const&) -> QueryHandlerImpl& = delete;
+
+    // Default move constructor and assignment operator
+    QueryHandlerImpl(QueryHandlerImpl&&) = default;
+    auto operator=(QueryHandlerImpl&&) -> QueryHandlerImpl& = default;
+
+    // Destructor
+    ~QueryHandlerImpl() = default;
+
+    /**
+     * Implementation of `QueryHandler::evaluate_node_id_value_pairs`.
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - TODO
+     */
+    [[nodiscard]] auto evaluate_node_id_value_pairs(
+            KeyValuePairLogEvent::NodeIdValuePairs const& auto_gen_node_id_value_pairs,
+            KeyValuePairLogEvent::NodeIdValuePairs const& user_gen_node_id_value_pairs
+    ) -> outcome_v2::std_result<AstEvaluationResult>;
+
+    /**
+     * Implementation of `QueryHandler::update_partially_resolved_columns` with new projected
+     * schema-tree node callback given as a template parameter.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param auto_gen_node_id_value_pairs
+     * @param user_gen_node_id_value_pairs
+     * @param new_projected_schema_tree_node_callback
+     * @return A result containing the evaluation result on success, or an error code indicating
+     * the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `handle_column_resolution_on_new_schema_tree_node`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto update_partially_resolved_columns(
+            bool is_auto_generated,
+            SchemaTree::NodeLocator const& node_locator,
+            SchemaTree::Node::id_t node_id,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    [[nodiscard]] auto get_resolved_column_to_schema_tree_node_ids() const -> std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>> {
+        return m_resolved_column_to_schema_tree_node_ids;
+    }
+
+private:
+    // Constructor
+    QueryHandlerImpl(
+            std::shared_ptr<clp_s::search::ast::Expression> query,
+            PartialResolutionMap auto_gen_namespace_partial_resolutions,
+            PartialResolutionMap user_gen_namespace_partial_resolutions,
+            std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> projected_columns,
+            ProjectionMap projected_column_to_original_key,
+            bool case_sensitive_match
+    )
+            : m_query{std::move(query)},
+              m_auto_gen_namespace_partial_resolutions{
+                      std::move(auto_gen_namespace_partial_resolutions)
+              },
+              m_user_gen_namespace_partial_resolutions{
+                      std::move(user_gen_namespace_partial_resolutions)
+              },
+              m_projected_columns{std::move(projected_columns)},
+              m_projected_column_to_original_key{std::move(projected_column_to_original_key)},
+              m_case_sensitive_search{case_sensitive_match} {}
+
+    // Methods
+    /**
+     * Handles column resolution of the given token iterator on the a new schema-tree tree node.
+     * @tparam NewProjectedSchemaTreeNodeCallbackType
+     * @param is_auto_generated
+     * @param node_id
+     * @param node_locator
+     * @param token_it The current resolved token in the column.
+     * @param new_projected_schema_tree_node_callback
+     * @return A void result on success, or an error code indicating the failure:
+     * - Forwards `ColumnDescriptorTokenIterator::next`'s return values.
+     * - Forwards `new_projected_schema_tree_node_callback`'s return values.
+     */
+    template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+    [[nodiscard]] auto handle_column_resolution_on_new_schema_tree_node(
+            bool is_auto_generated,
+            SchemaTree::Node::id_t node_id,
+            SchemaTree::NodeLocator const& node_locator,
+            ColumnDescriptorTokenIterator const& token_it,
+            NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+    ) -> outcome_v2::std_result<void>;
+
+    // Variables
+    std::shared_ptr<clp_s::search::ast::Expression> m_query;
+    PartialResolutionMap m_auto_gen_namespace_partial_resolutions;
+    PartialResolutionMap m_user_gen_namespace_partial_resolutions;
+    std::unordered_map<
+            clp_s::search::ast::ColumnDescriptor*,
+            std::unordered_set<SchemaTree::Node::id_t>>
+            m_resolved_column_to_schema_tree_node_ids;
+    std::vector<std::shared_ptr<clp_s::search::ast::ColumnDescriptor>> m_projected_columns;
+    ProjectionMap m_projected_column_to_original_key;
+    bool m_case_sensitive_search;
+};
+
+template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+auto QueryHandlerImpl::update_partially_resolved_columns(
+        bool is_auto_generated,
+        SchemaTree::NodeLocator const& node_locator,
+        SchemaTree::Node::id_t node_id,
+        NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+) -> outcome_v2::std_result<void> {
+    auto const parent_node_id{node_locator.get_parent_id()};
+    auto& partial_resolutions_to_update{
+            is_auto_generated ? m_auto_gen_namespace_partial_resolutions
+                              : m_user_gen_namespace_partial_resolutions
+    };
+    if (false == partial_resolutions_to_update.contains(parent_node_id)) {
+        return outcome_v2::success();
+    }
+
+    std::vector<std::pair<SchemaTree::Node::id_t, ColumnDescriptorTokenIterator>>
+            new_partial_resolutions;
+    for (auto const& token_it : partial_resolutions_to_update.at(parent_node_id)) {
+        OUTCOME_TRYV(handle_column_resolution_on_new_schema_tree_node(
+                is_auto_generated,
+                node_id,
+                node_locator,
+                token_it,
+                new_projected_schema_tree_node_callback
+        ));
+
+        // Handle partial resolution updates
+        if (SchemaTree::Node::Type::Obj != node_locator.get_type()) {
+            // If the schema-tree node is not of type `Obj`, it cannot have children, so no further
+            // resolution is possible from this node.
+            continue;
+        }
+
+        if (token_it.is_wildcard()) {
+            // For wildcard tokens, simulate potential matches by:
+            // - Adding a resolution that assumes this node matches part of the wildcard.
+            // - If this isn't the last wildcard token, also add a resolution for continuing the
+            //   match.
+            new_partial_resolutions.emplace_back(node_id, token_it);
+            if (false == token_it.is_last()) {
+                new_partial_resolutions.emplace_back(node_id, OUTCOME_TRYX(token_it.next()));
+            }
+            continue;
+        }
+
+        if (token_it.is_last() || token_it.get_token() != node_locator.get_key_name()) {
+            // If this is the last token, or the token doesn't match the node's key, then no further
+            // resolution is possible through the given node.
+            continue;
+        }
+
+        auto const next_token_it{OUTCOME_TRYX(token_it.next())};
+        new_partial_resolutions.emplace_back(node_id, next_token_it);
+        if (false == next_token_it.is_last() && next_token_it.is_wildcard()) {
+            // Handle the case where the wildcard matches nothing
+            new_partial_resolutions.emplace_back(node_id, OUTCOME_TRYX(next_token_it.next()));
+        }
+    }
+
+    for (auto const [node_id, token_it] : new_partial_resolutions) {
+        auto [it, inserted] = partial_resolutions_to_update.try_emplace(
+                node_id,
+                std::vector<ColumnDescriptorTokenIterator>{}
+        );
+        it->second.emplace_back(token_it);
+    }
+
+    return outcome_v2::success();
+}
+
+template <NewProjectedSchemaTreeNodeCallbackReq NewProjectedSchemaTreeNodeCallbackType>
+auto QueryHandlerImpl::handle_column_resolution_on_new_schema_tree_node(
+        bool is_auto_generated,
+        SchemaTree::Node::id_t node_id,
+        SchemaTree::NodeLocator const& node_locator,
+        QueryHandlerImpl::ColumnDescriptorTokenIterator const& token_it,
+        NewProjectedSchemaTreeNodeCallbackType new_projected_schema_tree_node_callback
+) -> outcome_v2::std_result<void> {
+    if ((false == token_it.is_last()
+         && false == OUTCOME_TRYX(token_it.next()).is_trailing_wildcard())
+        || false == token_it.match_schema_tree_node_type(node_locator.get_type())
+        || (false == token_it.is_wildcard() && token_it.get_token() != node_locator.get_key_name()))
+    {
path: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp
user: login: coderabbitai[bot]
body: @LinZhihao-723 Thank you for the clarification. I understand now that `m_case_sensitive_search` is specifically meant for string value comparisons during query evaluation, not for schema key name matching. Schema keys should indeed be matched with case sensitivity regardless of this flag's setting.

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: LinZhihao-723
PR: y-scope/clp#873
File: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp:320-324
Timestamp: 2025-05-05T01:12:18.525Z
Learning: In the CLP codebase, the `m_case_sensitive_search` flag is used only for actual string value comparisons during query evaluation, not for schema key name matching. Schema keys are always compared case-sensitively regardless of this flag's setting.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>




<!-- <agentComponentType>chat-answer</agentComponentType> -->

<!-- <rawResChunk><answer>@LinZhihao-723 Thank you for the clarification. I understand now that `m_case_sensitive_search` is specifically meant for string value comparisons during query evaluation, not for schema key name matching. Schema keys should indeed be matched with case sensitivity regardless of this flag's setting.

---

<details>
<summary>✏️ Learnings added</summary>

```
Learnt from: LinZhihao-723
PR: y-scope/clp#873
File: components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp:320-324
Timestamp: 2025-05-05T01:12:18.525Z
Learning: In the CLP codebase, the `m_case_sensitive_search` flag is used only for actual string value comparisons during query evaluation, not for schema key name matching. Schema keys are always compared case-sensitively regardless of this flag's setting.
```

> Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure [review instructions](https://docs.coderabbit.ai/guides/review-instructions). You can manage existing learnings in the [app](https://app.coderabbit.ai/learnings).

</details>

</answer></rawResChunk> -->

<!-- This is an auto-generated reply by CodeRabbit -->
created_at: 2025-05-05T01:12:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,82 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+
+#include <map>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+class ColumnQueryPossibleMatches {
+public:
+    ColumnQueryPossibleMatches(std::shared_ptr<SchemaTree> schema_tree)
+            : m_schema_tree{std::move(schema_tree)} {}
+
+    [[nodiscard]] auto get_matchable_types() const -> clp_s::search::ast::LiteralTypeBitmask {
+        return m_matchable_types;
+    }
+
+    [[nodiscard]] auto get_matchable_node_ids() const -> std::set<SchemaTree::Node::id_t> const& {
+        return m_matchable_node_ids;
+    }
+
+    [[nodiscard]] auto get_matchable_node_ids_from_literal_type(
+            clp_s::search::ast::LiteralType type
+    ) const -> std::vector<SchemaTree::Node::id_t>;
+
+    [[nodiscard]] auto get_matchable_node_ids_from_schema_tree_type(
+            SchemaTree::Node::Type type
+    ) const -> std::vector<SchemaTree::Node::id_t>;
+
+    auto set_matchable_node(SchemaTree::Node::id_t node_id, SchemaTree::Node::Type type) -> void {
+        m_matchable_node_ids.emplace(node_id);
+        m_matchable_types |= schema_tree_node_type_to_literal_types(type);
+    }
+
+    [[nodiscard]] auto serialize() const -> std::string;
path: components/core/src/clp/ffi/ir_stream/search/test/utils.hpp
user: login: gibber9809
body: Add a docstring? It is a bit unclear what this will serialize and why without reading more code.
created_at: 2025-05-05T16:20:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,82 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+
+#include <map>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+class ColumnQueryPossibleMatches {
path: components/core/src/clp/ffi/ir_stream/search/test/utils.hpp
user: login: gibber9809
body: Maybe a brief docstring for this class describing how it will be used in the tests?
created_at: 2025-05-05T16:23:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,460 @@
+#include <map>
+#include <memory>
+#include <set>
+#include <sstream>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <catch2/catch.hpp>
+#include <fmt/core.h>
+#include <fmt/format.h>
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/archive_constants.hpp"
+#include "../../../../../clp_s/search/ast/EmptyExpr.hpp"
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../../../clp_s/search/kql/kql.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../../../Value.hpp"
+#include "../QueryHandlerImpl.hpp"
+#include "utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+namespace {
+using clp_s::constants::cAutogenNamespace;
+using clp_s::constants::cDefaultNamespace;
+using clp_s::constants::cReservedNamespace1;
+using clp_s::search::ast::LiteralTypeBitmask;
+
+constexpr std::string_view cRefTestStr{"*test*"};
+constexpr value_int_t cRefTestInt{0};
+constexpr value_float_t cRefTestFloat{0.0};
+constexpr value_bool_t cRefTestBool{false};
+
+/**
+ * Generates all the matchable KQL expressions based on the column queries and their matchable
+ * types. For each column query `C` matched type `T`, we generate an expression of `C: cRefTestT`,
+ * where `cRefTestT` is any of the constants defined above.
path: components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp
user: login: gibber9809
body: ```suggestion
 * types. For each column query `C` and matched type `T`, we generate an expression of
 * `C: cRefTestT`, where `cRefTestT` is any of the constants defined above.
```
created_at: 2025-05-05T16:47:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,91 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+
+#include <map>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+/**
+ * Represent all the matchable clp-s literal types and the matchable schema-tree node IDs of a
+ * column query.
+ */
+class ColumnQueryPossibleMatches {
+public:
+    explicit ColumnQueryPossibleMatches(std::shared_ptr<SchemaTree> schema_tree)
+            : m_schema_tree{std::move(schema_tree)} {}
+
+    [[nodiscard]] auto get_matchable_types() const -> clp_s::search::ast::LiteralTypeBitmask {
+        return m_matchable_types;
+    }
+
+    [[nodiscard]] auto get_matchable_node_ids() const -> std::set<SchemaTree::Node::id_t> const& {
+        return m_matchable_node_ids;
+    }
+
+    [[nodiscard]] auto get_matchable_node_ids_from_literal_type(
+            clp_s::search::ast::LiteralType type
+    ) const -> std::vector<SchemaTree::Node::id_t>;
+
+    [[nodiscard]] auto get_matchable_node_ids_from_schema_tree_type(
+            SchemaTree::Node::Type type
+    ) const -> std::vector<SchemaTree::Node::id_t>;
+
+    auto set_matchable_node(SchemaTree::Node::id_t node_id, SchemaTree::Node::Type type) -> void {
+        m_matchable_node_ids.emplace(node_id);
+        m_matchable_types |= schema_tree_node_type_to_literal_types(type);
+    }
+
+    /**
+     * Serializes the underlying matchable types and matchable node IDs in human-readable form for
+     * debugging purposes.
+     * @return The serialized possible matches.
+     */
+    [[nodiscard]] auto serialize() const -> std::string;
+
+private:
+    std::shared_ptr<SchemaTree> m_schema_tree;
+    clp_s::search::ast::LiteralTypeBitmask m_matchable_types{};
+    std::set<SchemaTree::Node::id_t> m_matchable_node_ids;
+};
+
+/**
+ * Trivial implementation of `NewProjectedSchemaTreeNodeCallback` that always return success without
+ * doing anything.
+ * @param is_auto_generated
+ * @param
+ */
+[[nodiscard]] auto trivial_new_projected_schema_tree_node_callback(
+        bool is_auto_generated,
+        SchemaTree::Node::id_t node_id,
+        std::string_view projected_key_path
+) -> outcome_v2::std_result<void>;
+
+/**
+ * Gets all possible column queries to every single node in the schema-tree with a bitmask
+ * indicating all the potentially matched types.
+ * NOTE: It is assume that all the keys in the schema-tree to test don't contain escaped characters.
path: components/core/src/clp/ffi/ir_stream/search/test/utils.hpp
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Fix typo in comment**

There's a minor grammatical error in the note.

```diff
- * NOTE: It is assume that all the keys in the schema-tree to test don't contain escaped characters.
+ * NOTE: It is assumed that all the keys in the schema-tree to test don't contain escaped characters.
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
 * NOTE: It is assumed that all the keys in the schema-tree to test don't contain escaped characters.
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-05T17:44:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,91 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+
+#include <map>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+/**
+ * Represent all the matchable clp-s literal types and the matchable schema-tree node IDs of a
+ * column query.
+ */
+class ColumnQueryPossibleMatches {
+public:
+    explicit ColumnQueryPossibleMatches(std::shared_ptr<SchemaTree> schema_tree)
+            : m_schema_tree{std::move(schema_tree)} {}
+
+    [[nodiscard]] auto get_matchable_types() const -> clp_s::search::ast::LiteralTypeBitmask {
+        return m_matchable_types;
+    }
+
+    [[nodiscard]] auto get_matchable_node_ids() const -> std::set<SchemaTree::Node::id_t> const& {
+        return m_matchable_node_ids;
+    }
+
+    [[nodiscard]] auto get_matchable_node_ids_from_literal_type(
+            clp_s::search::ast::LiteralType type
+    ) const -> std::vector<SchemaTree::Node::id_t>;
+
+    [[nodiscard]] auto get_matchable_node_ids_from_schema_tree_type(
+            SchemaTree::Node::Type type
+    ) const -> std::vector<SchemaTree::Node::id_t>;
+
+    auto set_matchable_node(SchemaTree::Node::id_t node_id, SchemaTree::Node::Type type) -> void {
+        m_matchable_node_ids.emplace(node_id);
+        m_matchable_types |= schema_tree_node_type_to_literal_types(type);
+    }
+
+    /**
+     * Serializes the underlying matchable types and matchable node IDs in human-readable form for
+     * debugging purposes.
+     * @return The serialized possible matches.
+     */
+    [[nodiscard]] auto serialize() const -> std::string;
+
+private:
+    std::shared_ptr<SchemaTree> m_schema_tree;
+    clp_s::search::ast::LiteralTypeBitmask m_matchable_types{};
+    std::set<SchemaTree::Node::id_t> m_matchable_node_ids;
+};
+
+/**
+ * Trivial implementation of `NewProjectedSchemaTreeNodeCallback` that always return success without
+ * doing anything.
+ * @param is_auto_generated
+ * @param
+ */
+[[nodiscard]] auto trivial_new_projected_schema_tree_node_callback(
+        bool is_auto_generated,
+        SchemaTree::Node::id_t node_id,
+        std::string_view projected_key_path
+) -> outcome_v2::std_result<void>;
+
+/**
+ * Gets all possible column queries to every single node in the schema-tree with a bitmask
+ * indicating all the potentially matched types.
+ * NOTE: It is assume that all the keys in the schema-tree to test don't contain escaped characters.
+ * @param schema_tree
+ * @return A column-query-to-possible-matches map.
+ */
+[[nodiscard]] auto get_schema_tree_column_queries(std::shared_ptr<SchemaTree> const& schema_tree)
+        -> std::map<std::string, ColumnQueryPossibleMatches>;
+
+[[maybe_unused]] auto operator<<(
+        std::ostream& os,
+        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
+) -> std::ostream&;
path: components/core/src/clp/ffi/ir_stream/search/test/utils.hpp
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Consider adding a docstring for the output stream operator**

The output stream operator lacks documentation explaining its purpose and behaviour.

```diff
+/**
+ * Outputs the column query to possible matches map to the given output stream in a human-readable format.
+ * @param os The output stream to write to
+ * @param column_query_to_possible_matches The map of column queries to possible matches
+ * @return The output stream for chaining
+ */
[[maybe_unused]] auto operator<<(
        std::ostream& os,
        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
) -> std::ostream&;
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
/**
 * Outputs the column query to possible matches map to the given output stream in a human-readable format.
 * @param os The output stream to write to
 * @param column_query_to_possible_matches The map of column queries to possible matches
 * @return The output stream for chaining
 */
[[maybe_unused]] auto operator<<(
        std::ostream& os,
        std::map<std::string, ColumnQueryPossibleMatches> const& column_query_to_possible_matches
) -> std::ostream&;
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-05T17:44:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,91 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+
+#include <map>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+/**
+ * Represent all the matchable clp-s literal types and the matchable schema-tree node IDs of a
+ * column query.
+ */
+class ColumnQueryPossibleMatches {
+public:
+    explicit ColumnQueryPossibleMatches(std::shared_ptr<SchemaTree> schema_tree)
+            : m_schema_tree{std::move(schema_tree)} {}
+
+    [[nodiscard]] auto get_matchable_types() const -> clp_s::search::ast::LiteralTypeBitmask {
+        return m_matchable_types;
+    }
+
+    [[nodiscard]] auto get_matchable_node_ids() const -> std::set<SchemaTree::Node::id_t> const& {
+        return m_matchable_node_ids;
+    }
+
+    [[nodiscard]] auto get_matchable_node_ids_from_literal_type(
+            clp_s::search::ast::LiteralType type
+    ) const -> std::vector<SchemaTree::Node::id_t>;
+
+    [[nodiscard]] auto get_matchable_node_ids_from_schema_tree_type(
+            SchemaTree::Node::Type type
+    ) const -> std::vector<SchemaTree::Node::id_t>;
path: components/core/src/clp/ffi/ir_stream/search/test/utils.hpp
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Consider adding docstrings for the getter methods**

These methods lack documentation explaining their purpose and return values. Adding docstrings would improve code clarity and maintainability.

```diff
+    /**
+     * Gets the matchable node IDs that can match the given literal type.
+     * @param type The literal type to filter by
+     * @return A vector of schema tree node IDs that can match the given type
+     */
    [[nodiscard]] auto get_matchable_node_ids_from_literal_type(
            clp_s::search::ast::LiteralType type
    ) const -> std::vector<SchemaTree::Node::id_t>;

+    /**
+     * Gets the matchable node IDs of the specified schema tree node type.
+     * @param type The schema tree node type to filter by
+     * @return A vector of schema tree node IDs of the given type
+     */
    [[nodiscard]] auto get_matchable_node_ids_from_schema_tree_type(
            SchemaTree::Node::Type type
    ) const -> std::vector<SchemaTree::Node::id_t>;
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    /**
     * Gets the matchable node IDs that can match the given literal type.
     * @param type The literal type to filter by
     * @return A vector of schema tree node IDs that can match the given type
     */
    [[nodiscard]] auto get_matchable_node_ids_from_literal_type(
            clp_s::search::ast::LiteralType type
    ) const -> std::vector<SchemaTree::Node::id_t>;

    /**
     * Gets the matchable node IDs of the specified schema tree node type.
     * @param type The schema tree node type to filter by
     * @return A vector of schema tree node IDs of the given type
     */
    [[nodiscard]] auto get_matchable_node_ids_from_schema_tree_type(
            SchemaTree::Node::Type type
    ) const -> std::vector<SchemaTree::Node::id_t>;
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-05T17:44:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,91 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+
+#include <map>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+/**
+ * Represent all the matchable clp-s literal types and the matchable schema-tree node IDs of a
+ * column query.
+ */
+class ColumnQueryPossibleMatches {
+public:
+    explicit ColumnQueryPossibleMatches(std::shared_ptr<SchemaTree> schema_tree)
+            : m_schema_tree{std::move(schema_tree)} {}
+
+    [[nodiscard]] auto get_matchable_types() const -> clp_s::search::ast::LiteralTypeBitmask {
+        return m_matchable_types;
+    }
+
+    [[nodiscard]] auto get_matchable_node_ids() const -> std::set<SchemaTree::Node::id_t> const& {
+        return m_matchable_node_ids;
+    }
+
+    [[nodiscard]] auto get_matchable_node_ids_from_literal_type(
+            clp_s::search::ast::LiteralType type
+    ) const -> std::vector<SchemaTree::Node::id_t>;
+
+    [[nodiscard]] auto get_matchable_node_ids_from_schema_tree_type(
+            SchemaTree::Node::Type type
+    ) const -> std::vector<SchemaTree::Node::id_t>;
+
+    auto set_matchable_node(SchemaTree::Node::id_t node_id, SchemaTree::Node::Type type) -> void {
+        m_matchable_node_ids.emplace(node_id);
+        m_matchable_types |= schema_tree_node_type_to_literal_types(type);
+    }
path: components/core/src/clp/ffi/ir_stream/search/test/utils.hpp
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Consider documenting the `set_matchable_node` method**

This method lacks a docstring explaining its purpose and parameters. Adding documentation would improve code clarity.

```diff
+    /**
+     * Sets a node as matchable and updates the matchable types bitmask.
+     * @param node_id The ID of the node to mark as matchable
+     * @param type The type of the node to mark as matchable
+     */
    auto set_matchable_node(SchemaTree::Node::id_t node_id, SchemaTree::Node::Type type) -> void {
        m_matchable_node_ids.emplace(node_id);
        m_matchable_types |= schema_tree_node_type_to_literal_types(type);
    }
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
    /**
     * Sets a node as matchable and updates the matchable types bitmask.
     * @param node_id The ID of the node to mark as matchable
     * @param type    The type of the node to mark as matchable
     */
    auto set_matchable_node(SchemaTree::Node::id_t node_id, SchemaTree::Node::Type type) -> void {
        m_matchable_node_ids.emplace(node_id);
        m_matchable_types |= schema_tree_node_type_to_literal_types(type);
    }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-05T17:44:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,91 @@
+#ifndef CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+#define CLP_FFI_IR_STREAM_SEARCH_TEST_UTILS_HPP
+
+#include <map>
+#include <memory>
+#include <ostream>
+#include <set>
+#include <string>
+#include <string_view>
+#include <utility>
+#include <vector>
+
+#include <outcome/outcome.hpp>
+
+#include "../../../../../clp_s/search/ast/Literal.hpp"
+#include "../../../SchemaTree.hpp"
+#include "../utils.hpp"
+
+namespace clp::ffi::ir_stream::search::test {
+/**
+ * Represent all the matchable clp-s literal types and the matchable schema-tree node IDs of a
+ * column query.
+ */
+class ColumnQueryPossibleMatches {
+public:
+    explicit ColumnQueryPossibleMatches(std::shared_ptr<SchemaTree> schema_tree)
+            : m_schema_tree{std::move(schema_tree)} {}
+
+    [[nodiscard]] auto get_matchable_types() const -> clp_s::search::ast::LiteralTypeBitmask {
+        return m_matchable_types;
+    }
+
+    [[nodiscard]] auto get_matchable_node_ids() const -> std::set<SchemaTree::Node::id_t> const& {
+        return m_matchable_node_ids;
+    }
+
+    [[nodiscard]] auto get_matchable_node_ids_from_literal_type(
+            clp_s::search::ast::LiteralType type
+    ) const -> std::vector<SchemaTree::Node::id_t>;
+
+    [[nodiscard]] auto get_matchable_node_ids_from_schema_tree_type(
+            SchemaTree::Node::Type type
+    ) const -> std::vector<SchemaTree::Node::id_t>;
+
+    auto set_matchable_node(SchemaTree::Node::id_t node_id, SchemaTree::Node::Type type) -> void {
+        m_matchable_node_ids.emplace(node_id);
+        m_matchable_types |= schema_tree_node_type_to_literal_types(type);
+    }
+
+    /**
+     * Serializes the underlying matchable types and matchable node IDs in human-readable form for
+     * debugging purposes.
+     * @return The serialized possible matches.
+     */
+    [[nodiscard]] auto serialize() const -> std::string;
+
+private:
+    std::shared_ptr<SchemaTree> m_schema_tree;
+    clp_s::search::ast::LiteralTypeBitmask m_matchable_types{};
+    std::set<SchemaTree::Node::id_t> m_matchable_node_ids;
+};
+
+/**
+ * Trivial implementation of `NewProjectedSchemaTreeNodeCallback` that always return success without
+ * doing anything.
+ * @param is_auto_generated
+ * @param
+ */
+[[nodiscard]] auto trivial_new_projected_schema_tree_node_callback(
+        bool is_auto_generated,
+        SchemaTree::Node::id_t node_id,
+        std::string_view projected_key_path
+) -> outcome_v2::std_result<void>;
+
+/**
+ * Gets all possible column queries to every single node in the schema-tree with a bitmask
+ * indicating all the potentially matched types.
+ * NOTE: It is assumed that all the keys in the schema-tree to test don't contain escaped chars.
+ * @param schema_tree
path: components/core/src/clp/ffi/ir_stream/search/test/utils.hpp
user: login: coderabbitai[bot]
body: _🧹 Nitpick (assertive)_

**Minor inconsistency in comment**

The note mentions "escaped chars" but the standard term would be "escaped characters". Consider keeping terminology consistent throughout the codebase.

```diff
-* NOTE: It is assumed that all the keys in the schema-tree to test don't contain escaped chars.
+* NOTE: It is assumed that all the keys in the schema-tree to test don't contain escaped characters.
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
/**
 * Gets all possible column queries to every single node in the schema-tree with a bitmask
 * indicating all the potentially matched types.
 * NOTE: It is assumed that all the keys in the schema-tree to test don't contain escaped characters.
 * @param schema_tree
 */
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-05T18:53:21Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-04-30T23:59:12Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

This change introduces a complete implementation for IR stream search query handling in the `clp::ffi::ir_stream::search` module. It adds the `QueryHandlerImpl` class, which manages query preprocessing, projected columns, and partial column resolution for schema trees. The `QueryHandler` template is refactored to delegate to this new implementation. Error handling is expanded with new error codes. Comprehensive tests and supporting utilities for schema tree queries and projections are also added. Build configuration is updated to include the new source and test files.

## Changes

| File(s)                                                                                          | Change Summary                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
|--------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| components/core/CMakeLists.txt                                                                   | Added new source and test files for `ffi/ir_stream/search`: `QueryHandlerImpl.cpp`, `QueryHandlerImpl.hpp`, `test/test_QueryHandlerImpl.cpp`, `test/utils.cpp`, `test/utils.hpp` to the unit test sources list.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| components/core/src/clp/ffi/ir_stream/search/ErrorCode.cpp<br>components/core/src/clp/ffi/ir_stream/search/ErrorCode.hpp | Extended `ErrorCodeEnum` with eight new error codes related to query preprocessing, column projection, and AST handling. Updated the `ErrorCategory::message` specialization to provide descriptive messages for each new error code. No changes to exported function signatures.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.cpp<br>components/core/src/clp/ffi/ir_stream/search/QueryHandlerImpl.hpp | Introduced the `QueryHandlerImpl` class, implementing query preprocessing, projected columns creation, partial resolutions, and column resolution logic. Added a nested `ColumnDescriptorTokenIterator` class for token-wise traversal of column descriptors. Provided a static factory method, evaluation stub, and methods for updating partial resolutions and handling projections. Deleted copy operations and defaulted move operations.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| components/core/src/clp/ffi/ir_stream/search/QueryHandler.hpp                                    | Refactored the `QueryHandler` template to be a thin wrapper over `QueryHandlerImpl`. Implemented all methods inline, forwarding to the implementation. Updated the static factory method to accept query, projections, and case sensitivity. Added a private member for the implementation.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| components/core/src/clp/ffi/ir_stream/search/test/test_QueryHandlerImpl.cpp                      | Added comprehensive Catch2-based tests for `QueryHandlerImpl`, verifying column resolution and projection handling against a constructed schema tree. Included helper functions for generating KQL expressions, projections, and for serializing expected/actual results.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| components/core/src/clp/ffi/ir_stream/search/test/utils.cpp<br>components/core/src/clp/ffi/ir_stream/search/test/utils.hpp | Added utility class `ColumnQueryPossibleMatches` and related functions to support schema tree column query testing. Implemented methods for filtering and serializing possible matches, generating all valid column queries for a schema tree, and a trivial callback for projections. Provided operator overload for output streaming.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Client
    participant QueryHandler
    participant QueryHandlerImpl

    Client->>QueryHandler: create(callback, query, projections, case_sensitive)
    QueryHandler->>QueryHandlerImpl: create(query, projections, case_sensitive)
    QueryHandlerImpl->>QueryHandlerImpl: preprocess_query()
    QueryHandlerImpl->>QueryHandlerImpl: create_projected_columns_and_projection_map()
    QueryHandlerImpl->>QueryHandlerImpl: create_initial_partial_resolutions()
    QueryHandlerImpl-->>QueryHandler: QueryHandlerImpl instance
    QueryHandler-->>Client: QueryHandler instance

    Client->>QueryHandler: update_partially_resolved_columns(is_auto_generated, node_locator, node_id)
    QueryHandler->>QueryHandlerImpl: update_partially_resolved_columns(...)

    Client->>QueryHandler: evaluate_node_id_value_pairs(auto_gen_pairs, user_gen_pairs)
    QueryHandler->>QueryHandlerImpl: evaluate_node_id_value_pairs(...)
```

## Possibly related PRs

- [y-scope/clp#863](https://github.com/y-scope/clp/pull/863): Introduced the initial `QueryHandler` template API with stub methods; this PR completes and implements that interface.
- [y-scope/clp#754](https://github.com/y-scope/clp/pull/754): Refactored utility functions like `tokenize_column_descriptor` into `clp_s::search::SearchUtils`; this PR depends on those utilities for `QueryHandlerImpl` implementation.

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- DwQgtGAEAqAWCWBnSTIEMB26CuAXA9mAOYCmGJATmriQCaQDG+Ats2bgFyQAOFk+AIwBWJBrngA3EsgEBPRvlqU0AgfFwA6NPEgQAfACgjoCEYDEZyAAUASpETZWaCrKNwSPbABsvkCiQBHbGlcSHFcLzpIACIAMxJqAAoAawkweAoASi4AQVp6AHd8CmT0jDBefCJ/RGR4Zm5Itgxcanh8LFjiyAADAEVglwAJTFpIih7oyDlIbERKSAAZeAwALQRYNEIAdgAmAGZIRNtIDEcBBYAObf3MlBaKRWwGaXQeZ3E0X3rGkmbW8QdfixMKwDz9QayEYYMaUHoKBoddgaGAIZAnVDcD7At7zCjwV74EH+LzUKIMTYYUiIAA0fhI8X8GAYKyI1jsZkuABYAAx00aQJTcMi0ZBAk6c7YATjC+HpiHwXik6B8jFJVLA4lo8gKzgwrMQGjcYMgzG0WCI+C+ONwaPZKGQBBQDSa7HeFFwYpBtvBA0oUNG43hBXUsHweFNaGSrNBHnYGQ8lNhfDQYT+jTJarQtRR0DlaApBOVtqQdJ96DE2GtP1dLTaQNQZAYaG4DlJNEKoZWuJIWKoNCztVOaDY9Ah/uhyYAki6ermTQxsBQmaEKZhqfcGF5sEpY+h8up2lgiW9YgWCC5ILFsMzAVgBeWa392PXj96TWxbYpethuLQyQA+n2nw+LIAE1IqUi0ABTDbswGCIHOxoeEoiAMPi3B3pA6abIg8AAF6vGgtBgkyLyygoDzwAIeBHpARDYPASheCsREwpRUgtEe1pMKw6imtIiBoKQ9itDCziivOHgYsy26oW8FKiMkLGIKuHSxBkzAxrxjTwJg5EhralG4PiNFYQxTEkCx5C0vctDwM2WG2tQoKoKgGD4KEqYCP4UbaZSpD8uxzmhOQLy1M48i0Pgi7/K+sx/mSyDOHGoYLNF5D8HwHmhSQdB0CiABqXxMfFqm9nUsk7nGCFLjGXy+EUJSxF4+AFMgWJDgKxH2VSsz6qENCqcgTBSPifWwY4WAQduWFAvMzgUpAOQAMrQOg7GVCIYhHoaRgAHL5ged7WtFsUvlhv7/kNWXTL50YTQFRH+NhAAeukshE8grPZjlRC5JbIKpwkkFJ9raMwjpypUEiWVe4biSdvhMLuXR8NeHqkYKJBSK13D/DaJrjsMgZwleN47R0JW4DqXZYOWW2iJo5iWAAwiw/xA44ZouMh/AYF48i8QTQKA54qr+EEIQOlejzMHuaB4GGFB0ssawbFsYB7PsdIFGG9JS+V9Cpv4sMkAUsssHucwLD0KPKKo6hzpAABiiqtSGfWA0FOAEGaHbytwHTzJb8vluzSg2CoaihIgsjlfLBbJB5BSRLQpD0OWpsEhbkvBKpG30D9DlkpnmxefSZsW5U4XILqjr4kQpD+LQYNxwnKoKqcnlRCFKChMKFBo5Dm7+HFvjZ+byXsdFrw5fSYCTxbXy+dqlc5+SHPqMgN6RF1eAsG0DDr1P6AvViNutwdcoYz6fD2Whcx4R0dLxHQAhJ3S3RLwinOQJsyoLhkHpEwCgSh6CiztCcAGdpxBsCNAYfQxhwBQBFDiRWBBiBkGUAHYW7AuC8H4MIJmkhXgzHtlQR2mhtC6DAIYEwUA4BuWnr7QgpByD9k3qwfBfg0AWwcE4S85DFAOxjloHQSDkGmAMDpJELREAAHpQEkAUazAAslGEgyxhoaFwK9TgBhohGIMBYZaU4sEcNLvYbmkUcRripNIPmN5+JDVjuGCg5ENKREgCpUIPQVoAHkACqNhWYAFEAIuynIsMJK0ALONwNAEI8Jux2xYEHcg8ilHFBUeozR2jPS6P0cGbMb0sQwiiDMHqMYNLKnIBbLxrwSRWKdOWHosQNIKIyABVSvlmAKIWh42A8JZGZM0KiaQHhql3kqluaqe4nxxSwo05AxMAwwnGDORoGgGDcG4PCAUazJybJdBoWAeyej8lOObNMBdGm9FcQBP0JMNmUC2V4HZFygqZyKLMcQLEaZXngHvH8/zDS7P2YXUFwLDTnMhTeJQfA2muPhPZfwYhiiyBRIdfgd9piMS8PQJgGANIMX7EeOk1kHpEC/nwHSwKyokFwOIKkddKAeHsRnBBpicheBoOS4OFFyxKC3M4V8XoynFADt0bg2ABAsWPvGcQjiDCHXIEaIx0QjAQDAEYUZ7BFHKIGR4pRXhuAKI6fALpFAekmQSP0wZFIFFhOXMUCOoMIUcEMcYnl5j2E4KiAInm8gTycpVe4XoLrHgUFZmSS0LgOAcDYBFUg8IaAukzIgYULISr4XimjSNrqY0iLCWcZgJTkAkH0SKKIhlYD2BxsA+p2Ei2MGzGxMuJAMgKGXNIDJvU2SoXQvATCpCBIpukGDepgt9zgJbdGhQ8kUpcB6DkVSAARWQGARwOVjapF22htz+Eub0dmcEMDrukMOzCxQ8zJDIFOfl1BigBLwAE2IAAhBGooT09DPVNO9ZACKvgPcCpcJBf3rt/AqskVhHjbQ7P++Cv64P4AQ0eJDF6r0YQvKzXyd5QNHog3SNZYT3o1GfhgKciB9reC8Cew5zzZDQCoAhYer4rDZkQIRugzswkFnrc25sId/C4CXAhXE2b4AaWPr0mMVaMwrBjOWSgC7iX2TvGDKtSAWVslU90YTTS/jmn6mG2g3y9xKDPN4Vc7bRLAonky8TyBohBIwMnNqWB9N0pEdhMtGgtVGBMZYXlT6ZlCpNCK0kAqJMngU1KqIMq5UKr8+EAkiBtWQBxWGqGlnRDRfFbdPCRBt1iZqDieLHpEt8FlfKhyqXDyvAKOyyMSgADc/MZ2PhdM+Osc13zgijW6uNmLE3JqEqmgSX5M7pnbB4LNog9IsTzVheub0aAVM7EZaZ3FfDebbfMae9Bxsgz2pq7V0j9VZKNYgE1W5zWWutbavpAyEhDOdUW91Zy9levO76ixAb6BBtsaGp6GWDARp6EN4tShS2OHhGQRwOC6LdjafdxNlrE3dN6faxNjrhnDmTViAypSFOjEqfIapfUu1EFgLlC2+37bIGaQHJ0ABpQqFRtB8CnHYHHI4G2LXrYzjoGndpSRDs2kXS7/ArrXbgTd26tIMD3bgQj4Hf2YcvWhHDt78D3qo2Fl9b7P3fsQiRzDgH9QraPOr49JGoMfVg/BpmdBMMoZd5TDAWvsMjtw/h23h6NckaYyxzAiB2N3k47UHjtBf1MbI7wQSR5qO0Z8Ax9iPQ3MOD2Ql2g+0Rx9oLBBrTr0dPydbUzyNzIRG0CSfo+Xl6Uasjt8R3oywn1eGgLIYUbmFOu7jyRjvygu895INn38QcquD6hT0NRTKwz588u83rHZ4RjxMxTJ69AzJnw8Cne4/LIhoCgpAOte4hMiMNFluUnksY5Yoq1IgDk6TEpMoqK8HtaXzu6EmaybI2B7EkAR5msXoVh39aBngCoWZlo+VkdBVWlIt8sxVws4t3o89bpasUslV0tMs4d5ZRUhwodPsS0y0UksAlci9yI7YzUMcNIscbV+dmA8c3sKQK1TRFBpMCRM4jpO14YfA2oYwpcy0cFihEAvVIBdBehG8t0d0Vdsw1cg9j0DBJCoA/1FQpptdr0LwrdH1RCKBX1cB30v0EVEIVCpD1Dz0rdgMCMlCINzC1DHcYMaBUMEM3cNDkMHDehXCSEOgfcdc/c3UA8OhW8egvC1kw82Nih/Yjxo9uND1eNwiE9yNk8OhU86MwjztEFLt0k5FPRsl/BjUGBTUHtOlsc7URxXshcFEmNjk3lTlPVvUtV/t/VOEgcbFLxQd1xw07Rm17lFkDU9wjlSYKB3kRlSQhxrxbxdt1B5AC0/8Yx8dIApZ8RXhuxMBIBedRI+ljIq1xlH0eBHhYYl0eAR0rJWJ4Yat/Aa5k8+oliVj0tX8A8+pGYxBN5z1bIlMQICIYxgIlt5QNCZkLNiVelnhdM3hhjXlRjZx7hgZmRQY+ZlEfEqh6sVg5lUIvVEFIAAAqbEpjawa4x4WuVkDgXEiZXoJPG42oACFY2QeEKYr3dAPZFiIiBtKWeEm0VjCPaI+KTqQ7I4HoAJAw2IHIGEN2CgctEjAvV1AobvYUc3WfdmDAMaRJfAMjHTRCO4RAjwFYWVUIWkspCjI8FEA40TZzH/HzeSaTPcA09yOjW6a0zAeQEycPSPOiM8GFBBKAXEnwt4+gTDZAPDBIO8Uk7E8ku2fDEgICT3DsGCDwhCACUYaMtDXwjAACM0SFBkrCKk/k1MXxHEV4rCe9eOKFH0btLjGKPSAOAFEfMIMfaYdQM0RAZIa/A4sgBwcrZxdkwSHEYs2yAgA3AiV4BIJaYsp44M14SaeCbGAIm9CgWyAUDMx0MEEOIdXXec6YJOCLDwYoeAZ/bdXwPsk00IM0igCTbzZAAtSAp3IaMsfXIDG3IED0ojWyboNcwIulYIzoOwvaLEn0j4f4mwaQQE3aTYgaJbGwo8UM8M9CCcgCL4pbICACr4cCYC2aXaekimcybBThR0X5P460GaWiIEJco4AtDBNhHCqxAUG2CgAHNownKg6QO4D+eYCBemE0Qs94qaI7G0yEY82Yfk8sMEM1W2JARMg+ACVo0uNNOUQgvCWIeQSgrNYvXihC3NV4Ac4BdQfQy87oEc+tKcrAd8ucgSn0LAZsHwVZdS5bKMgirwVChUdCjoACWIOWHpVg2ARM1SWS8mTGBYIOWVObWMEOUimYF0saPCL2E0A01aaAL0nE7EuKmAKgSK60AtTjD0QCtC4ihCaCyHGyocpCrKlCoiu8Vy9y/Hby3ATC6YoEQeYeZKbGTCWAMADSecwaVKygISXwE8csWKtaFEcU7CATIFOAvgBTI0oEc/VMCQEqIleMmc7Q4oOkfiJQflLSGyC/QvFS8iGmYUKFFsYUGEKGA3fuXS7cntGoftX45CxzJy3KyMbgASv/JrYFWgZsMBWUA3GQSnA8PqHbKmb4I3Dcp0UaAK/wDSV6M/d6z6oldtX8707EgvInYvSAVmCYhSkuEMskgqxACSggKSqitfcmOq6aJzM8uuMEPFVMZ/TiRi3a+bEybSYoa60XXLDYii+iqxboWi7mgOZS4nEgb/U8iTDY/ba0m8HPKfDsBK3E2okY95V2c8TFSAefabfKk0YGcQY+CMic+ET8RfLKRSXpJKBWAQfAZUcqVsCiSoSA8iVMa8UCe4Q8DSscBWqEsY2EsSF4OkJsFsNsNoaKjwHMqIWkukeygE5yhCCOmM7i+CBczaGMuiDM/kVqPqc/QzBtBCQ8WGQFeYZlA0OW7ElaMTAQS4yAMJOa7cV8TW8EHGL4KsGgACDyJQeC6Cau4IJCjIRCKbI21AKLFKHfCMRcXtFoGdUWpq6IQ278eeAYloOgKYbzBBaHf+QMZTWAR4bAWncMUIG2FhHoXe3iKMiQXYPHXAaCGoGzNNesp0SoLEIgTMdahI+gaXV4KzViIuLAYg6Nd1fA52CNe2bCF0fAEsxAHmAefAcA7qqFKtF4UdIEBYr2OUds8DPwQQOYXAGyZASA8aNkA0l0qIiUwrAUEyi8RgL8hBGA0LeAiTbU7GUVGLCVSraVGrZLerHAlVKAPIOdbW+rM8DFS8Ge2gLgSAcwnoPh4+I+lgE+s+jgVSS+6QGzYAD26cF0PQSAVRk5RoRNWCskRIBRvHTYFuICEyYARPKajADR8O0SERjgKQQR4AQxjgLEDIJxi+8+vB1WHSqgUfYUD9Rs7MZIPQDRkE3AAAMkOJTK91sgto/0Mw8pzvECkHTOoApEyDazCKxJ4aiCwPq2EdEfEekbYAAlPvPsUbbFwGAHlyrsbtfCAsqY0a0fqJ0Y4AbprubtbqjKYlKcbrsu50QESDZxIFkGKm3BIE4wyEWCqCrvwQ4EOiUCnFoDGeCEmY3LCciYoqJrTK6fbt6fGe7vnLpGGdGb6bWemaIFmZaETQWZICWZWYmYGcolUkidou2ZbpET2c7v6Z7oycyOMWyN1RkVyLGUNRySKJKItTKMYIqIdU8pqMhDqOhO2ThV+x9RCz9WJsDQ6JDRBBy2NHchuTBGIgWHuQ/q2raWaeRfo0HEqiGPRw4Exw4HKL6RYKF3hEFuL1Wp63+A3o5RyWROf2PnmPXruM8uWMGHS34DGnsEUjNDCH8DeqMiMqWvXNbNCHAMeHttZJsgDksM0N9znN0JBvGK41BBch8bNstoWC0ok3WIUHPVVY/NjstqYhjGEdy0UgYGSBQAG2+u0uQAmNwH5FoDmo5PofIH0X9YwFfzBG9ZhsJThtEmoDmDpFE1WOLBNFte2NZAszGmkyFnSTaDUABVpiMjQjBHlbtX31832snSMEhLUcaANvNFaBWA6juqjtyolRVbIbEPQEfvbdjjldTGre7lQjpHmD7DJBnXIoPn5v+nYj5ukoFp2qFtsjmHdYDtDnHY8CnHXVywcYvAlWzctePYEulqlRGi/MgFhlTFTEkd6D0ZoANoX2/D7kpKJMEk0pishEnaZR3m4CifQ2Dgs0KsIg7ZKvupAuDgQUAcxqieOLYl6HaabqjN2Z6e+cOd7uEYolQ8zC6c2PXTAG+feB7oHfNALn6r/fpDK31ABu/pqc7vqaUb5WdgOK+C7iJEZCavTQzD1aukAnssFkcsgjoDjI+Nfem3Nb3sShukjrKtAr1ibRuQreMwVbyl3eSheibARn5QKlRFQFw9epkDv27nKGPxBESEEAQzuAFEs605DbDZZGQbOrPbELpDNG3X3LZBDETYkmjcTq/okH13qjbR8A/njYLWnXkC4qBxHY05rdQgQRyEOMkEzD47m3oBEsHj7u/B6Feok6mlE+joAhcvqR6RHYAmrY+aUHhFDuZxyrmgoNU4S7Ha6d+tvcoALeU3vKwH9kLAY87H86+vU0PFA4Smuj5aeu4BjBPBmlPyMtyzU6rcVd3aI6C/uBC+pT3Esq8Ei59eU/vD2UeF4CrIRIhwXAQ9UhyWXJ3MbhWGtANMmtSJjfdBAi7awiXJdbcIWo+KuQzJ3bi4dZ4u3O7V3P3OtD7Is1TCzvmCSckFmM/2EhRHZm4BDUHhIZemYiZToE8+tf4Ex/CxSmxmszgPM1gdbSQbZH3t6GKdkfKccuvphuVcwatjfuvwjVQj3KwEvY9ErWrRzvlQ5QagO70uyhuWB5W9HbW466hRM4TY+oC9tbpd7cNfPeofGsK3ocHqYYq3QOn0wPYcVS4mVXB24fyHJAQ8be0Zpe7E5eoIZaZZZdx3kc8qQgt7nV1at7NZt5aa8ETX8OWooGNf0ODFDGTZ1qvBVqEbfbHGfbb0XLj+svxqDcSEyF/UjdwHT9/XEr86V7ARz5I3EpdOBVZAAnz7hqL96FIFwGq76+r56Fr6K/ggAj7YoGr8OQG68ul+q8Vdq6jLrZz6NE98DQBH4Zj/kFw79+pd0cjN8rCYoDBIhKpa9qHf0gRNH+Ozj96FX5dETXw86c+cw76ew/pP0uY/BMI/3ZI76bI/nJH+Wkt5mwzQDmn739acE+buE7AgW/E6W98pf8Q6nbRToKhmrhd9uScR/jk3oBnc5qA4TLlYnf6ItFa+/DgIVyMoldcqZXHZubEq6Vs0AffPKAP3P58BQBWAanvzGuT8I2usvK/I/0vSRBcE+AdHs8xMhglugAoJgKwK4w88RYRPboAWhn5jEGBDIRWOTw4LKhF+HAlMOxGYAE9eBJWfgfoQrrCDZw0A5/jOXYGCNVBH/ejBqgBY6o9UILA1AURUS3Zii92KFlahd6VF8cCLCcCMW+zcA0WzRDFgu3aKCJcWjAMHHzBn6mshwiAgcGtkhox8ogblK2KmF16vgwAHQGdNAyHho1z8ziHluwCiAesKIjtOjFFAZCf1aWLPetCsGsgeBcO89cfrB3JKPsBGF4Kfjvz1pkgOWbUCsPA09CzpxuB5d0IXn5TiFcQxjXJlAwXpIp8wWAZKlDhSK1Ajw6+XsDUHjDB0JW/oK5Ee26AnhCydEVxkc0OoHZ5s7ZXOkjxago9NiakBCNoNaGph/B0wb6MqVC59Q1BrTBPuHyMg+gQ407T8DAwfALhRen8S4rqDATTcr6fKM/FQD2RRB1ix4PAMfTg6cVs44YRADOllQvRYYHoKsL1T4ApDfg/wdIcnx/Byd+mUHETn/2ggADZ8h/dDsfw7qn91hvdEngfjKEgiBYFxJ0GjB+Gdpu0e3T1qzT7Si53WWIoEKmDgGZhbhdvY4Rvwjq8CAa6dNkOWD24Hcz8pEDwHnATBXxLu/LY4Uvx0FrYzMmQhgC0KhQ3cXoZwvQeQThIO0JReEXcFKM+HeswY/IgcNdFTBsBmAFwPgD0GYA0lIQAEV6jahrDsEeovcOUGGEJSuQ6gqQvrHRBs5MwUuqoJPLDBhGiQ5UzodERdFAqi0YwP9N1KQUcCJp1ai+Q6LgBXwYi48Z+FrGPGtatxySxLRFN3Athok5IREY6Ltmao1pmQUrAtMlTra8VKWKAz2jCXkpgw0xMOUGHCnILokPAIQv4KWPQB1ANWyADyMiQcQS98oSogJGNAahlgruZrA/OeGSi8cEAWAAoECNy5MiJI2kBqBzSwAJCOhZQ18LrAj5O1fAuHC2vZFeAwxLIX9YofcD3AVjKAVDYLLATCygUdeyBPXmgRlrVZPAdWE3mli4aQBS6R8aPoI1qHSd6hL7D8fJTTCv9fQXY5MMAH2jmxfSHYFaCOxYx5RbmsaCLknDlIkA9AIybfKHAkK9AegdsPZOYUfYUUKGE5RILhIKD4S6AhEggcRJICkTLRyQSidQOTK/d8BxmIgaSLbrSik4mQcwpIVUIaN6epTORgoyZ58oVGWE8YHoDaziNGJ5hAgPRMYlMTuALE8fsfDYkJ9EgikpSVxJ4m0A+JxmASUJPInetRJFXOLpJLNDSSB+MEYSTSDsmSFnGiAPodBEwgUBgA92HpGyyGSJoFCiaCxq92sZ/tgptjRNEsKinON1h7jOxnJipCv4zUsUt3kLgSmqRE0w+XxpRICa4AmywTUJsHAibAdUytIdKXE2RjtpEmeEZJlGW77mE7gdCfgOCJkZqTGe/wqplSz0kGSsmUAbMflyAHFUQI+ItClBBb4IRyCu/HSWTE1ERDE4DDArM10FgmTGJEKcwmxMWk/8Sua0pbrZKUlKSOpDoAmvgG2aA4gp90yQs5LNACSbmIiaZo5AMxNTImuzVqADJVjpSvpaAH6fMxERY5oIoUMkQNN0AqSRpJTMpvIwvqaSqmIXJiNNMkKmSjJww+4O+PJaYjpOR434cgy2mODuxzbE6WZPOkHxJuQnO6itKco3T4ygzdqfgA/ziUtmK7PHhDKImKtfpSgf6c+h8zHDgZnzUGRLPekfTIZ0M25nDOq67t26SMoaapPRkaSJpwAHGbQDxkMTZpatOoSSP8kn8DmlIzaecN2lyw3g0Qo6bIHplnTJCbEs2Rh3JGWyBmd0j6ScwebnMZmnETgDDMWbLMzmTzDZqwneYez9mXdSkfLPul+zw5UzQOXM1ub3Nk56zIGYJUoDRyyRscn5vOQ1kozcAx9MaRjIqbKMmOdTO8A0xsyGyCZkhJ0BsSKEXFSZ2/cmcUGZG9dqZLyJtvRmdnMTXZTM92fnKw6UifZickZv7O5wXMrmwc9OWHPGZrMRo2c/mWQHNmey45AzBOUpKTnLzZ5qc65iHLuZLzVmEc7OW83YSbyC52HYucNNLmjTtZmM3WdXI6ZHg65fKBuYZKxJKlQSOgk8DbNol7TB55kyQgpgVT8QqWnEvCXHScnCySJIiMiZAI8n1kvJcCnyYQJq67M5J3rDJjNMJmgLzCEChyFAu2kd90pDkuBYrMVZuSUFIktBXgO8m99sFnzXBckF3mSE9B8wlwB6JGLwUXQA0/SfjJ/lb80u8AlCNQHtF/AnRRCrheQqVqujaSfCqEgIsaDCKjZTRC7ECyuz5Ebsd2M1NYKexMEqi72VxAokeR6DPkLgrRS0SxaeDg0diXwQYFS7NpXE9gdxJ4mBQ6lkomgt/OaBZoNB/AYIHOsqASS3JWhBaTsTTP7kjITBLQD8WjloKMt6CzLGFqy1KnxSOAKKBmkLQEo1jqoeFOUGaFRzSxDMO8KKpKJNCxpS5sAXYLLELxNRkgkIjwO1So5lL20JGaIMos9HxIcRS0pbGzLE6EjOZ0QE9NILEBNVn63i7LgSCoBDIS4vgfxAgogwFCtOOIOaviFjHtijgASYQHSCnAtA6QpdFWJAC/SKg6QLsVqNQDs7sRve9AFnLtAQCtgBKexKgJMpB7TkHirwPaXuGl6JcoUAs5AGzj6CLBDSr3cXiNSWgqtWKiWDil2hqz4AJhwvSMLUrrLykjghy4Nq7GuXYrzlXgY5SZDuDdgLa22edgLONgzwxBNmXJapQlxxhxhlGbTvy0dF5DwC+YSACCrBUGkCpNKd0GxUPwcqDS4YsQBZkvgUQE+K/BRTCXX7wkwY7is9oSBlYNRZWBAgFYR1lnHtVqVwnbj0Eumszf+q0//pzNIHTAzODvaQPyFqCUAr+KDItGKG1FLgzKISh1pEA+XlhzwyIqOot3jKahCAhHDMrN1ZQ7tqO/oNelCShQ6QUod3ROIO2OF7h+8fpT7qBURSkJ6Avy8sBSvBUTCJuXGG1fJiCDUwsUfMeYOpgiVbCulPS/hYV1WEdAxldIWnuWCEhsBVV6nMdgXV/D4pQgXQRcPyXqrJ0KhBxIFS1JiYV1SVgmNdqpSB4xkVA3iJbgUN3qK84avFBPk1UFFGifao4iPuLQaCAoDS7wyZCOpmQohUu7CttJhHAwzjzYcIuBa2tW6ac5euDGMEAM55ZtpYL697oMvkAEjPlEmPCFpGixdY5Q5YNpYNBCDartu03M9U6HRTFAHlRq2AQOoQhDVHuoEVal5GtX88ZOCsSsIRQQ1HrQK3fBNe9AHz8x62BgFaJPilR8sQ4riZlaCDW45dSWWFXaCugFmpNals6qMskACAOUXuOajafRCxbArQV2aplbAxI1JryBkK+deFXrICgLVHWJvli3EmtT4Qw62tVgEpESbs0AcH9ZpshWFkuNv6uuKGEXWV8JIiADrIcjxAQVCI60zefjQzLwhbNGlZAIGoNB2JFq7fXLD+uv4HsK6SgGiE3Dm54A9SLSiJcgGrqlQEB5cXuesliVXUmYM6D9QKD/6ryPiUKAzRRyHYKR0kkQaGv8urZ0hKBFq3iswBswjpvEdo9FUqzDARgLy3KELFr3CyATGGhWECRgSSwQSGsZvTLFxPLVZ1fRIjXoNAFiTQAAIrMVaGEkSDdL3RvS/VXiMNXszjVHxaIHSGiAABtS1AAF1Nttg5gHtvxx7aqWO26IBnw/FpJEQoLMwRCysGPYDtpip1OYssXSrtkEKMIlAAG3uKhtz/FdGNrWiTbpts2qtaoprVIb1tMQbbRpD20HajtnlE7eQrO0XbUkuisFoUQsGQsHt6S+1E9tgAWKQgBO1SE8je0fIPtWJAbReOtBMb0YLG3kZoIMaWSfEKwZICDGyDhEcp3OPKZlKZjFBudGMvBnoEnYeMk0LYfnbyuF35SmUwAGhXlFFkkAVZuAEJho3Y3d8uNAEHjXxsZW7RGd+U5mlSFKY5xf1LdSdb7Qymi7uA4u/XXyswxMYrASKvCMLw0S1LpAjUqWRnwp03IqdvgGnaTUZLDajgj7KlGzvolqFOdbjZxllP525TQp1u7xp3hqmBNmyITSXWNjF2x6vG5ugujLpWXy7FdyuoTZYm/5IbBmGe8vlXGN0WrU9Fuq3Zntt2Qh7dyKyIM7pNpu6XmHur7V7sGEdDfdWZOiAHsZ1wTg9pAdnaoV6Bl6xW+IDSg5o9lOaWwuutPZbon18rQp0u2XQrpPn5629ETD3VkSMHAtrtpg/RZYMMXY7nsuO+weYtoheBwUP2Wxe4KzXA5OieLZxUwivUNJvFCY2sK0Ov17C6dYtdiCwIup1pUcRMJ3qkoO1xSKQiaHJRaooh88wNqkObiCCK1rcVW3y9VqT1YjuasRUSomPXv9CN7HdzetJiuQCHLkXIXiflFCnTZFgkue7ALb8ofa0DNOsK9iszs7y1axQfAVAw+trZj5PiVUAdHl1FAURa+qKtcCiv82QqFsLIGTBwdrI7LEgPQLFSQGbjQBf0Vyq0Ikl/T4qEgGADQyRmKgUATlrIQw6ejNSmGqQGh25TwSE0DwZ1Uh3zPuwlQeRygIqwaGPj7GubbKOfEQ5cX9h3dD8lADodrR3Igg1dKKnZQpucMBb2VuIa3fSCTzw9yhGAFLgq0R48RhJfurCD0GZp3sHK6C6JrGRYX98cFwklJB1COKvjdYCAJaF8F1AllJ61ibUT2TrSLqzRcYDpEzF/KAMBWfeoEMprr6lHiBmAjA+puE1vBAevy9A5CBzbBqnQ+reCHbod3UQSDLu3ujMAarREwuQoW0G1R7qdUT83Va0H1S1osGmaAFPqBmpNCPBPIu7ASiUrrDtsEjW5FYU8xPA8rrdvFaQxHTmC7i2QhYQlEyHWXJCcR9ADA2DAGMUDSYyADA2soFDtGIwpff/Euss2v4mpao8ErUg8DRaITcxrEMyhCMdQFghHNg1QL7Buh4TXApcCuHW5HlXY+lUajY34gfqGYqxlFd3zq27daTboLpgAHJkA+7UsmPgszoolweEXGPIAgW3cfB71EE3L3Px7Ea0+J7ruRtFIPy9S2xe1ITxUED1ch5AOw2dwSXlhAeoaRatSeChghu0QcJvSUNINrF7w/8RwJgEXgJB/wKKyPIJWm4SNuuGlPw8IzBhANae8gxFFgFZgABqCM1eGDKXqWj9aUpP/LKyVJi4rKSlCOAED/gxNu0CzG0liDMAaqzOnyLYgLSR5dMTWv8bQ1yzCogJHWkECwzAl5NIJjWc3ibOk7DaV0ke3nVFPX157mW8MjRgQZcBEG1jJAFvSuUTTN9IjkQRzRVRYAAQayvjarmPkSBVSvgokutncDCabS4DNBFwSkvgAMFz9I4KA7ABgPJIsS8042H9vH0i6o9vZzff2eq6Dn4yKxu0+OekCTmmUHGyQzOdn1znXRIxwfiuYfPKyOAG5sfFuaak7nTd4IcA4ebSXHnmCmS6A9kovNzSd+HZ283rrwbo1XzDejk+sZNosEp9vhqC8cJguo1HeyS53jjpPMoWzzaFnyliRdj/7Z0dAFdFrPUkvzWO2MqBgbIyMFGW6TCjBUBf8nsLEgHU45bnsfM9NsVy+w3ebBR0UFYLT7GixAbovIX8c555i1AFYtk12LI2iRiLozK17c2eF89G+eINjmHTiAFXd+bEtjHJW0gBffI3CmmMezKy7feE2Uu0rqL+52i0hdPM6WaqWJAJAIJTA3njLdjJFbC0iZAH+wfO4AK5dit9Jwm1e0y8vrpBDnZAI5p3bZe8u+Xdz8Fo80wWCtMXQre+nIofuuzgtMd926Fkhbx1E7cACia/bCjv1/YH99i6xF4KcXdFwcriolu6dJaf69VYKZwZUcMv9RKxSSgKxpaCsMWQrHLWC/koeBPBwou3a3jlbytEWyDtRhyMLmZAB1vAZtW09ZYkMrkK6sxtU8lDjUFwzh6+gBnaDQkBjRDD7cKTwAGHUHm5vQJ61chjj1SkjMwriH1GnMeBFz1odsTDwbShATw4Nu9TLz4O7gXDAlF8fJAyEwamUGbDwOWEBtBMcQCN6G38qZSE3SDxm341CioPsp6AWU1w7EZkDtp2DXaPFJDd8B1sisFx9bnWyDMIdOOcoApScVw7Nz8gUx8m04aUANq5OMYcdRfliOw3SyJoNm7VobJ1SCbBYUBAOkFgw3hbcoHw0OX7iVR+UoRgEDqRaAcrYALp8oKvGM28qUu9YoGtrbeD5H/iZ66Ew8nxBCWijEksS+UfcnJApruvPHrKMOvNDewrQl4bjw3Is4/RiN9Vb5k000GKaYtFo7XHlA0qkTscOGAyHiCTL0jblTTu7aGOYK/JTl264HaAnB29Yod1oPeiaphSh6X1hIZkL+srKDk7EZo/aJbA7sbrl4XlbliWMYArLo5j873VlXhR5wqAd28OpVXnXRzv6nhS2KZNLRCOoB+bFza+AdBfOEfMsoio2wfdu+MYZW+2JS5gjcAWppgrqcSt8BrWFAa5UXBwaV27DZAYzcafBKmnu75px1vCbvqEX7TGxiLf0SENzxRAgkWxHCX/BfUBQhZMALIa4LHwvxG5GivyQpn0AHZoFB8PJpYgI0d2Md47E8D3g/ieULWgCcBqQLtbUC9Zg3qw3AnYFTeuBbJn4u2v4XCDf90e5RaYpqX5rCFyA0tYqsPD60TZkQ+IXCIxTxCfDxKRwDXN+MSAtUoG1OfFszn2x6fNgWETH3RWWCVTUC7DKfNK62BkTBR5xuF6OaVH25jnXee7M57+JIs2SwOfsM/nNgxj/83tIXOWsHKQ/MR+VckfSOILwoci8xfUddnHG2jpQPnvsfg3ZzLjsS0PxCcb7fHJAfx6FfUf6zYbDj9XV00SCxPFdaspiNJesdy7Hz8Tj3YE5F28qG0pFocqY+guMO50Bd1pf/pEfqOuL403i3rP4saMXbKFb2wPhLusLZJwkySzzO+D4115Re4O1k90c5OKeClivd5OLJIVbQxTiws40yulO49FlqaMPfysbH7LwxqrjVzLurFS9pT9y5FKscuTFW3lxG9JI71P850u9C+7CyvsSzpWlAe+6HuwuJpUr9qeKxFeADJXnG3zkcPFdsgrP09azuvSw+HNsOCr+j43cosJpz3jHXJxALvsMF0IpEqCQAyCC5pZq8E1zXhPwhxYXDF0oidQOIloT0IUEv8dQO3XxpLxxOwMD0JS8xeQAuQAARnZeqApQ7LtAOy4ACsewBgOy55ACA34AANhID7B+X+wLkAcG2CxAGA/Lj6vEC5D7AGAtALkCy4YSChxX/Lrl1y4YC7B2XUoPl+K/Fe7AZXXITM2/CNf8vLg1wAQFKAYACAeQuwUQLsHFfavqX7LvYLQEuBoAuQ2wUV4G92DOuBARrrkMK/Ze0BOXsr/lzyHFeZm9guwDpPsHFf0AkEOry4AkHFcOvaAPIflxq9oC7AXgsQOV7QAEDbAc77Ly18K+texBxX2wXl82FLfeuIA0wS4LQHFdSgBA+rrkN26DfsuXgDAHkGgG2D/hYgprqULQG1iXB9XDICdx/HLpZvqX0rwt/y9Le7Bg3UofYPsAuAcv2XXILkF24YAOvR3JAUt+y8uAMAuQ5r9l2eBXcYudX/L/l2K/9fXupQXIKV5cB5Cju0Am7611a6lCXB3Xeb/l1KB3dyvb3IIVdx24A/xBaAsQfl2gGdc52eXJANV/sEt4kAE3aAcV/u7QC7AS3b7/IMe/ZftuoAR72t9B/5eiBYg2wKt5e5lf7ASA4roNx/D2CgffXjH418K6Lepg4PUAbd/y/Feiv8gw7691W4dd5vdgrrgQEG+3cKe3XUod11G6lCxBZ3mb599S/FfEQq3379lwyB5AOvTXeUUd6K9Tdfu93BYD6jyDoAMAeXU7jwEJ+mALvd3JAZTyQBvfOvrXAHhII27leqvU3zbk90G65BfuZQrnh19sDQBoBvPCQfYFKElfXueQzb4j6J6dfKvdgibl1yK/2BpfX3CQSj4wCUDEQLXjn3d/sFTd/uP4hbgd1G9A+8hZ3Vb2IH24U/7AAPJXjT8a9oD7Ac3ib9V/kDFcCBsPObpDyK6/f2v7X/Hv93q55CwedPHbkgNW+Q/uuHX7LpL267E90A93t7jN6B4g9Nvx347mV/+H3clfeXXIe1zO9w8WveQu764HR4g/xeCvPIJQJcFiAFfQPe7pt3J5K/BvC3p3yV665jfNvZ3qn+IHy9iD2fNX13pt425UBIeJ3JXoj5cDzdTupQor4N2q7QCHeeQvIAn7F4EDcgJ3XL9H265LeWuWXBgHV8LFpdMR6XOMDeNBDQT6AgAA=== -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=y-scope/clp&utm_content=873):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: LinZhihao-723
created_at: 2025-05-02T22:42:54Z
body: @coderabbit review
author_association: MEMBER
user: login: coderabbitai[bot]
created_at: 2025-05-02T22:42:59Z
body: <!-- This is an auto-generated reply by CodeRabbit -->
<details>
<summary>✅ Actions performed</summary>

Review triggered.

> Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

</details>
author_association: CONTRIBUTOR