//////////////////////////////////////////////////////////////////////////////// /// DISCLAIMER /// /// Copyright 2017 ArangoDB GmbH, Cologne, Germany /// /// 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 ArangoDB GmbH, Cologne, Germany /// /// @author Andrey Abramov /// @author Vasiliy Nabatchikov //////////////////////////////////////////////////////////////////////////////// #include "ExpressionFilter.h" #include "Aql/AqlItemBlock.h" #include "Aql/AqlValue.h" #include "Aql/AstNode.h" #include "formats/empty_term_reader.hpp" #include "search/all_filter.hpp" #include "search/all_iterator.hpp" #include "search/score_doc_iterators.hpp" #include "utils/hash_utils.hpp" #include namespace { template inline irs::filter::prepared::ptr compileQuery( arangodb::iresearch::ExpressionCompilationContext const& ctx, irs::index_reader const& index, irs::order::prepared const& order, irs::boost_t boost) { typedef typename std::enable_if::value, T>::type type_t; irs::bstring stats(order.stats_size(), 0); // skip filed-level/term-level statistics because there are no fields/terms order.prepare_collectors().finish(&stats[0], index); return irs::filter::prepared::make(ctx, std::move(stats), boost); } /////////////////////////////////////////////////////////////////////////////// /// @class NondeterministicExpressionIterator /////////////////////////////////////////////////////////////////////////////// class NondeterministicExpressionIterator final : public irs::basic_doc_iterator_base { public: NondeterministicExpressionIterator( irs::sub_reader const& reader, irs::byte_type const* stats, irs::order::prepared const& order, uint64_t docs_count, arangodb::iresearch::ExpressionCompilationContext const& cctx, arangodb::iresearch::ExpressionExecutionContext const& ectx, irs::boost_t boost) : max_doc_(irs::doc_id_t(irs::type_limits::min() + docs_count - 1)), expr_(cctx.plan, cctx.ast, cctx.node.get()), ctx_(ectx) { TRI_ASSERT(ctx_.ctx && ctx_.trx); // make doc_id accessible via attribute attrs_.emplace(doc_); // set estimation value estimate(max_doc_); // set scorers prepare_score( order, order.prepare_scorers(reader, irs::empty_term_reader(docs_count), stats, attributes(), boost) ); } virtual ~NondeterministicExpressionIterator() noexcept { destroy(); } virtual bool next() override { return !irs::type_limits::eof(seek(doc_.value + 1)); } virtual irs::doc_id_t seek(irs::doc_id_t target) override { while (target <= max_doc_) { destroy(); // destroy old value before assignment val_ = expr_.execute(ctx_.trx, ctx_.ctx, destroy_); if (val_.toBoolean()) { break; } ++target; } doc_.value = target <= max_doc_ ? target : irs::type_limits::eof(); return doc_.value; } virtual irs::doc_id_t value() const noexcept override { return doc_.value; } private: FORCE_INLINE void destroy() noexcept { if (destroy_) { val_.destroy(); } } irs::document doc_; irs::doc_id_t max_doc_; // largest valid doc_id arangodb::aql::Expression expr_; arangodb::aql::AqlValue val_; arangodb::iresearch::ExpressionExecutionContext ctx_; bool destroy_{false}; }; // NondeterministicExpressionIterator /////////////////////////////////////////////////////////////////////////////// /// @class NondeterministicExpressionQuery /////////////////////////////////////////////////////////////////////////////// class NondeterministicExpressionQuery final : public irs::filter::prepared { public: explicit NondeterministicExpressionQuery(arangodb::iresearch::ExpressionCompilationContext const& ctx, irs::bstring&& stats, irs::boost_t boost) noexcept : irs::filter::prepared(std::move(stats), boost), _ctx(ctx) { } virtual irs::doc_iterator::ptr execute(const irs::sub_reader& rdr, const irs::order::prepared& order, const irs::attribute_view& ctx) const override { auto const& execCtx = ctx.get(); if (!execCtx || !static_cast(*execCtx)) { // no execution context provided return irs::doc_iterator::empty(); } // set expression for troubleshooting purposes execCtx->ctx->_expr = _ctx.node.get(); return irs::doc_iterator::make( rdr, stats(), order, rdr.docs_count(), _ctx, *execCtx, boost() ); } private: arangodb::iresearch::ExpressionCompilationContext _ctx; }; // NondeterministicExpressionQuery /////////////////////////////////////////////////////////////////////////////// /// @class DeterministicExpressionQuery /////////////////////////////////////////////////////////////////////////////// class DeterministicExpressionQuery final : public irs::filter::prepared { public: explicit DeterministicExpressionQuery(arangodb::iresearch::ExpressionCompilationContext const& ctx, irs::bstring&& stats, irs::boost_t boost) noexcept : irs::filter::prepared(std::move(stats), boost), _ctx(ctx) { } virtual irs::doc_iterator::ptr execute(const irs::sub_reader& segment, const irs::order::prepared& order, const irs::attribute_view& ctx) const override { auto const& execCtx = ctx.get(); if (!execCtx || !static_cast(*execCtx)) { // no execution context provided return irs::doc_iterator::empty(); } // set expression for troubleshooting purposes execCtx->ctx->_expr = _ctx.node.get(); arangodb::aql::Expression expr(_ctx.plan, _ctx.ast, _ctx.node.get()); bool mustDestroy = false; auto value = expr.execute(execCtx->trx, execCtx->ctx, mustDestroy); arangodb::aql::AqlValueGuard guard(value, mustDestroy); if (value.toBoolean()) { return irs::doc_iterator::make( segment, stats(), order, segment.docs_count(), boost()); } return irs::doc_iterator::empty(); } private: arangodb::iresearch::ExpressionCompilationContext _ctx; }; // DeterministicExpressionQuery } // namespace namespace arangodb { namespace iresearch { /////////////////////////////////////////////////////////////////////////////// /// --SECTION-- ExpressionCompilationContext implementation /////////////////////////////////////////////////////////////////////////////// size_t ExpressionCompilationContext::hash() const noexcept { return irs::hash_combine( irs::hash_combine(irs::hash_combine(1610612741, arangodb::aql::AstNodeValueHash()(node.get())), plan), ast); } /////////////////////////////////////////////////////////////////////////////// /// --SECTION-- ExpressionExecutionContext implementation /////////////////////////////////////////////////////////////////////////////// DEFINE_ATTRIBUTE_TYPE(ExpressionExecutionContext); /////////////////////////////////////////////////////////////////////////////// /// --SECTION-- ByExpression implementation /////////////////////////////////////////////////////////////////////////////// DEFINE_FILTER_TYPE(ByExpression); DEFINE_FACTORY_DEFAULT(ByExpression); ByExpression::ByExpression() noexcept : irs::filter(ByExpression::type()) {} bool ByExpression::equals(irs::filter const& rhs) const noexcept { auto const& typed = static_cast(rhs); return irs::filter::equals(rhs) && _ctx == typed._ctx; } size_t ByExpression::hash() const noexcept { return _ctx.hash(); } irs::filter::prepared::ptr ByExpression::prepare(irs::index_reader const& index, irs::order::prepared const& order, irs::boost_t filter_boost, irs::attribute_view const& ctx) const { if (!bool(*this)) { // uninitialized filter return irs::filter::prepared::empty(); } filter_boost *= this->boost(); if (!_ctx.node->isDeterministic()) { // non-deterministic expression, make non-deterministic query return compileQuery(_ctx, index, order, filter_boost); } auto* execCtx = ctx.get().get(); if (!execCtx || !static_cast(*execCtx)) { // no execution context provided, make deterministic query return compileQuery(_ctx, index, order, filter_boost); } // set expression for troubleshooting purposes execCtx->ctx->_expr = _ctx.node.get(); // evaluate expression bool mustDestroy = false; arangodb::aql::Expression expr(_ctx.plan, _ctx.ast, _ctx.node.get()); auto value = expr.execute(execCtx->trx, execCtx->ctx, mustDestroy); arangodb::aql::AqlValueGuard guard(value, mustDestroy); return value.toBoolean() ? irs::all().prepare(index, order, filter_boost) : irs::filter::prepared::empty(); } } // namespace iresearch } // namespace arangodb