1
0
Fork 0
arangodb/arangod/IResearch/ExpressionFilter.cpp

285 lines
10 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// 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 <type_traits>
namespace {
template <typename T>
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<std::is_base_of<irs::filter::prepared, T>::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<type_t>(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<irs::type_t::doc_id_t>::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<irs::type_t::doc_id_t>::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<irs::type_t::doc_id_t>::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<arangodb::iresearch::ExpressionExecutionContext>();
if (!execCtx || !static_cast<bool>(*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<NondeterministicExpressionIterator>(
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<arangodb::iresearch::ExpressionExecutionContext>();
if (!execCtx || !static_cast<bool>(*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<irs::all_iterator>(
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<ByExpression const&>(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<NondeterministicExpressionQuery>(_ctx, index, order, filter_boost);
}
auto* execCtx = ctx.get<arangodb::iresearch::ExpressionExecutionContext>().get();
if (!execCtx || !static_cast<bool>(*execCtx)) {
// no execution context provided, make deterministic query
return compileQuery<DeterministicExpressionQuery>(_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