//////////////////////////////////////////////////////////////////////////////// /// DISCLAIMER /// /// Copyright 2016 by EMC Corporation, All Rights Reserved /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// /// Copyright holder is EMC Corporation /// /// @author Andrey Abramov /// @author Vasiliy Nabatchikov //////////////////////////////////////////////////////////////////////////////// #include #include "parser_context.hpp" #include "analysis/analyzers.hpp" #include "analysis/token_attributes.hpp" #include "analysis/token_stream.hpp" #include "search/all_filter.hpp" #include "search/boolean_filter.hpp" #include "search/phrase_filter.hpp" #include "search/range_filter.hpp" #include "search/term_filter.hpp" #include "utils/locale_utils.hpp" #include "query_builder.hpp" #if defined (__GNUC__) #pragma GCC diagnostic push #if (__GNUC__ >= 7) #pragma GCC diagnostic ignored "-Wimplicit-fallthrough=0" #endif #endif NS_LOCAL // ----------------------------------------------------------------------------- // --SECTION-- private members // ----------------------------------------------------------------------------- // by default no transformation is performed and value is treated verbatim const irs::iql::query_builder::branch_builder_function_t RANGE_EE_BRANCH_BUILDER = []( irs::iql::proxy_filter& root, const std::locale& locale, const iresearch::string_ref& field, void* cookie, const std::vector& args )->bool { iresearch::bstring minValue; iresearch::bstring maxValue; bool bMinValueNil; bool bMaxValueNil; if (args.size() != 2 || !args[0].value(minValue, bMinValueNil, locale, cookie) || !args[1].value(maxValue, bMaxValueNil, locale, cookie)) { return false; } auto& range = root.proxy().field(field); if (!bMinValueNil) { range.term(std::move(minValue)) .include(false); } if (!bMaxValueNil) { range.term(std::move(maxValue)) .include(false); } return true; }; const irs::iql::query_builder::branch_builder_function_t RANGE_EI_BRANCH_BUILDER = []( irs::iql::proxy_filter& root, const std::locale& locale, const iresearch::string_ref& field, void* cookie, const std::vector& args )->bool { iresearch::bstring minValue; iresearch::bstring maxValue; bool bMinValueNil; bool bMaxValueNil; if (args.size() != 2 || !args[0].value(minValue, bMinValueNil, locale, cookie) || !args[1].value(maxValue, bMaxValueNil, locale, cookie)) { return false; } auto& range = root.proxy().field(field); if (!bMinValueNil) { range.term(std::move(minValue)) .include(false); } if (!bMaxValueNil) { range.term(std::move(maxValue)) .include(true); } return true; }; const irs::iql::query_builder::branch_builder_function_t RANGE_IE_BRANCH_BUILDER = []( irs::iql::proxy_filter& root, const std::locale& locale, const iresearch::string_ref& field, void* cookie, const std::vector& args )->bool { iresearch::bstring minValue; iresearch::bstring maxValue; bool bMinValueNil; bool bMaxValueNil; if (args.size() != 2 || !args[0].value(minValue, bMinValueNil, locale, cookie) || !args[1].value(maxValue, bMaxValueNil, locale, cookie)) { return false; } auto& range = root.proxy().field(field); if (!bMinValueNil) { range.term(std::move(minValue)) .include(true); } if (!bMaxValueNil) { range.term(std::move(maxValue)) .include(false); } return true; }; const irs::iql::query_builder::branch_builder_function_t RANGE_II_BRANCH_BUILDER = []( irs::iql::proxy_filter& root, const std::locale& locale, const iresearch::string_ref& field, void* cookie, const std::vector& args )->bool { iresearch::bstring minValue; iresearch::bstring maxValue; bool bMinValueNil; bool bMaxValueNil; if (args.size() != 2 || !args[0].value(minValue, bMinValueNil, locale, cookie) || !args[1].value(maxValue, bMaxValueNil, locale, cookie)) { return false; } if (bMinValueNil && bMaxValueNil) { // exact equivalence optimization for nil value root.proxy().field(field).term(iresearch::bytes_ref::NIL); } else if (!bMinValueNil && !bMaxValueNil && minValue == maxValue) { // exact equivalence optimization root.proxy().field(field).term(std::move(minValue)); } else { auto& range = root.proxy().field(field); if (!bMinValueNil) { range.term(std::move(minValue)) .include(true); } if (!bMaxValueNil) { range.term(std::move(maxValue)) .include(true); } } return true; }; const irs::iql::query_builder::branch_builder_function_t SIMILAR_BRANCH_BUILDER = []( irs::iql::proxy_filter& root, const std::locale& locale, const iresearch::string_ref& field, void* cookie, const std::vector& args )->bool { iresearch::bstring value; bool bValueNil; if (args.size() != 1 || !args[0].value(value, bValueNil, locale, cookie)) { return false; } const iresearch::string_ref value_ref(bValueNil ? iresearch::string_ref::NIL : iresearch::ref_cast(value)); auto tokens = irs::analysis::analyzers::get( "text", irs::text_format::text, irs::locale_utils::name(locale) ); if (!tokens || !tokens->reset(value_ref)) { return false; } auto& node = root.proxy().field(field); for (auto& term = tokens->attributes().get(); tokens->next();) { node.push_back(term->value()); } return true; }; // ----------------------------------------------------------------------------- // --SECTION-- private functions // ----------------------------------------------------------------------------- class parse_context; class ErrorNode: public iresearch::filter { public: ErrorNode(): filter(ErrorNode::type()) {} iresearch::filter::prepared::ptr prepare( const iresearch::index_reader&, const iresearch::order::prepared&, boost_t, const iresearch::attribute_view&) const override { iresearch::filter::prepared::ptr result; // null-ptr result return result; } private: friend parse_context; std::string sError; DECLARE_FILTER_TYPE(); }; DEFINE_FILTER_TYPE(ErrorNode); //////////////////////////////////////////////////////////////////////////////// /// @brief proxy_filter specialized for iresearch::filter::ptr //////////////////////////////////////////////////////////////////////////////// class LinkNode: public irs::iql::proxy_filter_t> { public: LinkNode(iresearch::filter* link): proxy_filter_t(LinkNode::type()) { filter_ = ptr(link); } LinkNode(const LinkNode& other): proxy_filter_t(LinkNode::type()) { filter_ = other.filter_; } template static ptr make(Args&&... args) { PTR_NAMED(LinkNode, ptr, std::forward(args)...); return ptr; } private: DECLARE_FILTER_TYPE(); }; DEFINE_FILTER_TYPE(LinkNode); class RootNode: public iresearch::Or { public: DECLARE_FACTORY_DEFAULT(); private: friend parse_context; iresearch::order order; size_t nLimit; }; DEFINE_FACTORY_DEFAULT(RootNode); class parse_context: public irs::iql::parser_context { public: parse_context( const std::string& sQuery, const std::locale& locale, void* cookie, const irs::iql::functions& functions, const irs::iql::query_builder::branch_builders& branch_builders ); irs::iql::query build(); irs::iql::query buildError(); private: static const irs::iql::parser::semantic_type SUCCESS; static const irs::iql::parser::semantic_type UNKNOWN; const irs::iql::query_builder::branch_builders& m_branch_builders; void* m_cookie; const std::locale& m_locale; irs::iql::parser::semantic_type append_function_arg( irs::iql::function_arg::fn_args_t& buf, const irs::iql::parser::semantic_type& node_id ) const; // @return SUCCESS or ID of failed node, or UNKNOWN for self irs::iql::parser::semantic_type children( iresearch::boolean_filter& node, const std::vector& children ) const; // @return SUCCESS or ID of failed node, or UNKNOWN for self irs::iql::parser::semantic_type eval( iresearch::bstring& buf, const query_node& src ) const; // @return SUCCESS or ID of failed node, or UNKNOWN for self template irs::iql::parser::semantic_type eval( typename fn_type::contextual_buffer_t& buf, const typename fn_type::contextual_function_t& fn, const std::vector& args, const ctx_args_type&... ctx_args ) const; // @return SUCCESS or ID of failed node, or UNKNOWN for self query_node const& find_node( irs::iql::parser::semantic_type const& value ) const { // force visibility only of const fn for GCC, otherwise it tries private fn return parser_context::find_node(value); } template // @return SUCCESS or ID of failed node, or UNKNOWN for self irs::iql::parser::semantic_type init(T& node, const query_node& src) const; irs::iql::parser::semantic_type initRange( irs::iql::proxy_filter& node, const iresearch::string_ref& field, const irs::iql::parser::semantic_type& min_node_id, bool min_inclusive, const irs::iql::parser::semantic_type& max_value_id, bool max_inclusive ) const; irs::iql::parser::semantic_type initSimilar( irs::iql::proxy_filter& node, const iresearch::string_ref& field, const irs::iql::parser::semantic_type& value_id ) const; irs::iql::parser::semantic_type order( iresearch::order& node, const std::vector>& order ) const; // @return SUCCESS or ID of failed node }; const irs::iql::parser::semantic_type parse_context::SUCCESS = std::numeric_limits::max(); // init() success const irs::iql::parser::semantic_type parse_context::UNKNOWN = std::numeric_limits::max() - 1; // init() unknown failure parse_context::parse_context( const std::string& sQuery, const std::locale& locale, void* cookie, const irs::iql::functions& functions, const irs::iql::query_builder::branch_builders& branch_builders ): parser_context(sQuery, functions), m_branch_builders(branch_builders), m_cookie(cookie), m_locale(locale) { } // add implementation before any calls to the function template irs::iql::parser::semantic_type parse_context::eval( typename fn_type::contextual_buffer_t& buf, const typename fn_type::contextual_function_t& fn, const std::vector& args, const ctx_args_type&... ctx_args ) const { irs::iql::function_arg::fn_args_t fnArgs; fnArgs.reserve(args.size()); // build function argument list for (size_t i = 0, count = args.size(); i < count; ++i) { auto errorNodeId = append_function_arg(fnArgs, args[i]); if (SUCCESS != errorNodeId) { return UNKNOWN == errorNodeId ? args[i] : errorNodeId; } } return fn(buf, ctx_args..., fnArgs) ? SUCCESS : UNKNOWN; } template<> // add implementation before any calls to the function irs::iql::parser::semantic_type parse_context::init( irs::iql::proxy_filter& node, const query_node& src ) const { assert( (query_node::NodeType::FUNCTION == src.type && src.pFnBoolean) || query_node::NodeType::EQUAL == src.type || query_node::NodeType::LIKE == src.type ); node.boost(src.fBoost); if (query_node::NodeType::FUNCTION == src.type && src.pFnBoolean) { return eval( node, function(*(src.pFnBoolean)), src.children, m_locale, m_cookie ); } assert(src.children.size() == 2); // 2 - left and right side of operator auto& left = find_node(src.children[0]); iresearch::bstring fieldBuf; { auto errorNodeId = eval(fieldBuf, left); if (SUCCESS != errorNodeId) { return UNKNOWN == errorNodeId ? src.children[0] : errorNodeId; } } auto field = iresearch::ref_cast(fieldBuf); switch(src.type) { case query_node::NodeType::EQUAL: { // iresearch:iql::parser implementation returns range on right auto& right = find_node(src.children[1]); assert(query_node::NodeType::RANGE == right.type); assert(right.children.size() == 2); // 2 - min and max value of range auto errorNodeId = initRange( node, field, right.children[0], right.bBeginInclusive, right.children[1], right.bEndInclusive ); return UNKNOWN == errorNodeId ? src.children[1] : errorNodeId; } case query_node::NodeType::LIKE: { auto errorNodeId = initSimilar(node, field, src.children[1]); return UNKNOWN == errorNodeId ? src.children[1] : errorNodeId; } default: {} // NOOP } return UNKNOWN; } template<> // add implementation before any calls to the function irs::iql::parser::semantic_type parse_context::init( iresearch::Or& node, const query_node& src ) const { assert(query_node::NodeType::UNION == src.type); node.boost(src.fBoost); return children(node, src.children); } template<> // add implementation before any calls to the function irs::iql::parser::semantic_type parse_context::init( iresearch::And& node, const query_node& src ) const { assert(query_node::NodeType::INTERSECTION == src.type); node.boost(src.fBoost); return children(node, src.children); } template<> // add implementation before any calls to the function irs::iql::parser::semantic_type parse_context::init( iresearch::Not& node, const query_node& src ) const { assert( (query_node::NodeType::FUNCTION == src.type && src.pFnBoolean) || query_node::NodeType::EQUAL == src.type || query_node::NodeType::LIKE == src.type ); return init(node.filter(), src); } template<> // add implementation before any calls to the function irs::iql::parser::semantic_type parse_context::init( iresearch::all& node, const query_node& src ) const { assert(query_node::NodeType::BOOL_TRUE == src.type); node.boost(src.fBoost); return SUCCESS; } irs::iql::query parse_context::build() { auto state = current_state(); irs::iql::query result; if (!state.pnFilter) { return buildError(); } query_node root; root.type = query_node::NodeType::UNION; root.children.push_back(*state.pnFilter); result.filter = RootNode::make(); auto errorNodeId = init(*static_cast(result.filter.get()), root); // initialize filter if (SUCCESS != errorNodeId) { std::stringstream error; error << "filter conversion error, node: @" << errorNodeId << std::endl; print(error, *state.pnFilter, false, true); result.filter = ErrorNode::make(); result.error = &(static_cast(result.filter.get())->sError); *(result.error) = error.str(); return result; } // initialize order if (!state.order.empty()) { errorNodeId = order(static_cast(result.filter.get())->order, state.order); if (SUCCESS != errorNodeId) { std::string sDelim = ""; std::stringstream error; error << "order conversion error, node: @" << errorNodeId << std::endl; for (auto orderTerm: state.order) { error << sDelim; print(error, orderTerm.first, false, true); error << (orderTerm.second ? " ASC": " DESC"); sDelim = ", "; } result.filter = ErrorNode::make(); result.error = &(static_cast(result.filter.get())->sError); *(result.error) = error.str(); return result; } result.order = &(static_cast(result.filter.get())->order); } // initialize limit if (state.pnLimit) { result.limit = &(static_cast(result.filter.get())->nLimit); *(result.limit) = *(state.pnLimit); } return result; } irs::iql::query parse_context::buildError() { auto state = current_state(); std::stringstream error; error << "@"; if (!state.pError) { error << "(" << state.nOffset << "): parse error"; } else { error << "(" << "[" << state.pError->nStart << " - " << state.pError->nEnd << "], " << state.nOffset << "): " << state.pError->sMessage; } irs::iql::query result; result.filter = ErrorNode::make(); result.error = &(static_cast(result.filter.get())->sError); *(result.error) = error.str(); return result; } irs::iql::parser::semantic_type parse_context::append_function_arg( irs::iql::function_arg::fn_args_t& buf, const irs::iql::parser::semantic_type& node_id ) const { irs::iql::function_arg::fn_args_t fnArgs; auto& node = find_node(node_id); switch(node.type) { case query_node::NodeType::UNION: buf.emplace_back( std::move(fnArgs), [this, node_id]( irs::iql::proxy_filter& node, const std::locale&, void* const&, const irs::iql::function_arg::fn_args_t& args )->bool { return args.empty() && SUCCESS == init(node.proxy(), find_node(node_id)); } ); return SUCCESS; case query_node::NodeType::INTERSECTION: buf.emplace_back(std::move(fnArgs), [this, node_id]( irs::iql::proxy_filter& node, const std::locale&, void* const&, const irs::iql::function_arg::fn_args_t& args )->bool { return args.empty() && SUCCESS == init(node.proxy(), find_node(node_id)); }); return SUCCESS; case query_node::NodeType::BOOL_TRUE: buf.emplace_back(std::move(fnArgs), [this, node_id]( irs::iql::proxy_filter& node, const std::locale&, void* const&, const irs::iql::function_arg::fn_args_t& args )->bool { auto& argNode = find_node(node_id); return args.empty() && SUCCESS == init( argNode.bNegated ? node.proxy().filter() : node.proxy(), argNode ); }); return SUCCESS; case query_node::NodeType::FUNCTION: fnArgs.reserve(node.children.size()); // build function argument list for (size_t i = 0, count = node.children.size(); i < count; ++i) { auto& argNodeId = node.children[i]; auto errorNodeId = append_function_arg(fnArgs, argNodeId); if (SUCCESS != errorNodeId) { return UNKNOWN == errorNodeId ? argNodeId : errorNodeId; } } if (node.pFnBoolean) { if (node.bNegated) { auto fnBranchArg = [this, node_id]( irs::iql::proxy_filter& node, const std::locale& locale, void* const& cookie, const std::vector& args )->bool { auto& argNode = find_node(node_id); return argNode.pFnBoolean && function(*(argNode.pFnBoolean))( node.proxy().filter(), locale, cookie, args ); }; if (node.pFnSequence) { buf.emplace_back(std::move(fnArgs), function(*(node.pFnSequence)), std::move(fnBranchArg)); } else { buf.emplace_back(std::move(fnArgs), std::move(fnBranchArg)); } return SUCCESS; } if (node.pFnSequence) { buf.emplace_back(std::move(fnArgs), function(*(node.pFnSequence)), function(*(node.pFnBoolean))); } else { buf.emplace_back(std::move(fnArgs), function(*(node.pFnBoolean))); } return SUCCESS; } if (node.pFnSequence) { buf.emplace_back(std::move(fnArgs), function(*(node.pFnSequence))); return SUCCESS; } return node_id; // not a supported function type case query_node::NodeType::EQUAL: // fall through case query_node::NodeType::LIKE: buf.emplace_back(std::move(fnArgs), [this, node_id]( irs::iql::proxy_filter& node, const std::locale&, void* const&, const irs::iql::function_arg::fn_args_t& args )->bool { auto& argNode = find_node(node_id); return args.empty() && SUCCESS == init( argNode.bNegated ? node.proxy().filter() : node, argNode ); }); return SUCCESS; case query_node::NodeType::SEQUENCE: buf.emplace_back(iresearch::ref_cast(iresearch::string_ref(node.sValue))); return SUCCESS; default: {} // NOOP } return node_id; // unknown arg type } // initialize node children irs::iql::parser::semantic_type parse_context::children( iresearch::boolean_filter& node, const std::vector& children ) const { for (auto& childId: children) { auto errorNodeId = childId; auto& child = find_node(childId); switch (child.type) { case query_node::NodeType::UNION: errorNodeId = init(node.add(), child); break; case query_node::NodeType::INTERSECTION: errorNodeId = init(node.add(), child); break; case query_node::NodeType::FUNCTION: if (!child.pFnBoolean) { break; // only boolean functions supported as branches } // fall through case query_node::NodeType::EQUAL: case query_node::NodeType::LIKE: errorNodeId = child.bNegated ? init(node.add(), child) : init(node.add(), child); break; default: {} // NOOP } // on error terminate traversal if (SUCCESS != errorNodeId) { return UNKNOWN == errorNodeId ? childId : errorNodeId; } } return SUCCESS; } irs::iql::parser::semantic_type parse_context::eval( iresearch::bstring& buf, const query_node& src ) const { switch(src.type) { case query_node::NodeType::SEQUENCE: buf.append(iresearch::ref_cast(iresearch::string_ref(src.sValue))); break; case query_node::NodeType::FUNCTION: if (src.pFnSequence) { auto errorNodeId = eval( buf, function(*(src.pFnSequence)), src.children, m_locale, m_cookie ); // on error terminate traversal if (SUCCESS != errorNodeId) { return errorNodeId; } break; } default: return UNKNOWN; } return SUCCESS; } irs::iql::parser::semantic_type parse_context::order( iresearch::order& node, const std::vector>& order ) const { for (auto orderTerm: order) { auto& ascending = orderTerm.second; auto& childId = orderTerm.first; auto& childNode = find_node(childId); switch(childNode.type) { case query_node::NodeType::FUNCTION: { if (!childNode.pFnOrder) { return childId; // no order function } auto errorNodeId = eval( node, function(*(childNode.pFnOrder)), childNode.children, m_locale, m_cookie, ascending ); // on error terminate traversal if (SUCCESS != errorNodeId) { return UNKNOWN == errorNodeId ? childId : errorNodeId; } break; } case query_node::NodeType::SEQUENCE: // field-term order is not yet supported by iresearch::order default: return childId; } } return SUCCESS; } irs::iql::parser::semantic_type parse_context::initRange( irs::iql::proxy_filter& node, const iresearch::string_ref& field, const irs::iql::parser::semantic_type& min_node_id, bool min_inclusive, const irs::iql::parser::semantic_type& max_value_id, bool max_inclusive ) const { auto& min_node = find_node(min_node_id); auto& max_node = find_node(max_value_id); irs::iql::function_arg::fn_args_t args; args.reserve(2); if (query_node::NodeType::UNKNOWN == min_node.type) { args.emplace_back(iresearch::bytes_ref::NIL); } else { auto errorNodeId = append_function_arg(args, min_node_id); if (SUCCESS != errorNodeId) { return UNKNOWN == errorNodeId ? min_node_id : errorNodeId; } } if (query_node::NodeType::UNKNOWN == max_node.type) { args.emplace_back(iresearch::bytes_ref::NIL); } else { auto errorNodeId = append_function_arg(args, max_value_id); if (SUCCESS != errorNodeId) { return UNKNOWN == errorNodeId ? min_node_id : errorNodeId; } } // ............................................................................. // build a branch off of 'node' for IQL field/value // ............................................................................. if (min_inclusive) { if (max_inclusive) { return m_branch_builders.range_ii(node, m_locale, field, m_cookie, args) ? SUCCESS : UNKNOWN; } return m_branch_builders.range_ie(node, m_locale, field, m_cookie, args) ? SUCCESS : UNKNOWN; } if (max_inclusive) { return m_branch_builders.range_ei(node, m_locale, field, m_cookie, args) ? SUCCESS : UNKNOWN; } return m_branch_builders.range_ee(node, m_locale, field, m_cookie, args) ? SUCCESS : UNKNOWN; } irs::iql::parser::semantic_type parse_context::initSimilar( irs::iql::proxy_filter& node, const iresearch::string_ref& field, const irs::iql::parser::semantic_type& value_id ) const { irs::iql::function_arg::fn_args_t args; args.reserve(1); auto errorNodeId = append_function_arg(args, value_id); if (SUCCESS != errorNodeId) { return UNKNOWN == errorNodeId ? value_id : errorNodeId; } // build a branch off of 'node' for IQL field/value return m_branch_builders.similar(node, m_locale, field, m_cookie, args) ? SUCCESS : UNKNOWN; } NS_END NS_ROOT NS_BEGIN(iql) // ----------------------------------------------------------------------------- // --SECTION-- constructors and destructors // ----------------------------------------------------------------------------- query_builder::branch_builders::branch_builders( const branch_builder_function_t* v_range_ee /*= nullptr*/, const branch_builder_function_t* v_range_ei /*= nullptr*/, const branch_builder_function_t* v_range_ie /*= nullptr*/, const branch_builder_function_t* v_range_ii /*= nullptr*/, const branch_builder_function_t* v_similar /*= nullptr*/ ): range_ee(v_range_ee ? *v_range_ee : RANGE_EE_BRANCH_BUILDER), range_ei(v_range_ei ? *v_range_ei : RANGE_EI_BRANCH_BUILDER), range_ie(v_range_ie ? *v_range_ie : RANGE_IE_BRANCH_BUILDER), range_ii(v_range_ii ? *v_range_ii : RANGE_II_BRANCH_BUILDER), similar(v_similar ? *v_similar : SIMILAR_BRANCH_BUILDER) { } query_builder::query_builder( const functions& iql_functions /*= functions::DEFAULT()*/, const branch_builders& branch_builders /*= branch_builders::DEFAULT()*/ ): branch_builders_(branch_builders), iql_functions_(iql_functions) { } // ----------------------------------------------------------------------------- // --SECTION-- public functions // ----------------------------------------------------------------------------- /*static*/ const query_builder::branch_builders& query_builder::branch_builders::DEFAULT() { static const branch_builders BRANCH_BUILDERS( nullptr, nullptr, nullptr, nullptr, nullptr ); return BRANCH_BUILDERS; } query query_builder::build( const std::string& query, const std::locale& locale, void* cookie /*= nullptr*/, proxy_filter* root /*= nullptr*/ ) const { parse_context ctx(query, locale, cookie, iql_functions_, branch_builders_); parser parser(ctx); if (parser.parse()) { return ctx.buildError(); } if (!root) { return ctx.build(); } auto result = ctx.build(); // link the query into both the root and result.filter via two LinkNodes if (!result.error) { auto& link = root->proxy(result.filter.get()); result.filter.release(); result.filter = LinkNode::make(link); } return result; } // ----------------------------------------------------------------------------- // --SECTION-- private functions // ----------------------------------------------------------------------------- DEFINE_FILTER_TYPE(proxy_filter); DEFINE_FACTORY_DEFAULT(proxy_filter); NS_END // iql NS_END // NS_ROOT #if defined (__GNUC__) #pragma GCC diagnostic pop #endif // ----------------------------------------------------------------------------- // --SECTION-- END-OF-FILE // -----------------------------------------------------------------------------