blob: 921829db6a95aa1062340ee4c92e953e7e4f029c [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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.
#include "gandiva/expr_decomposer.h"
#include <memory>
#include <stack>
#include <string>
#include <unordered_set>
#include <vector>
#include "arrow/util/logging_internal.h"
#include "gandiva/annotator.h"
#include "gandiva/dex.h"
#include "gandiva/function_holder_maker_registry.h"
#include "gandiva/function_registry.h"
#include "gandiva/function_signature.h"
#include "gandiva/in_holder.h"
#include "gandiva/node.h"
#include "gandiva/regex_functions_holder.h"
namespace gandiva {
// Decompose a field node - simply separate out validity & value arrays.
Status ExprDecomposer::Visit(const FieldNode& node) {
auto desc = annotator_.CheckAndAddInputFieldDescriptor(node.field());
DexPtr validity_dex = std::make_shared<VectorReadValidityDex>(desc);
DexPtr value_dex;
if (desc->HasOffsetsIdx()) {
value_dex = std::make_shared<VectorReadVarLenValueDex>(desc);
} else {
value_dex = std::make_shared<VectorReadFixedLenValueDex>(desc);
}
result_ = std::make_shared<ValueValidityPair>(validity_dex, value_dex);
return Status::OK();
}
// Try and optimize a function node, by substituting with cheaper alternatives.
// eg. replacing 'like' with 'starts_with' can save function calls at evaluation
// time.
const FunctionNode ExprDecomposer::TryOptimize(const FunctionNode& node) {
if (node.descriptor()->name() == "like") {
return LikeHolder::TryOptimize(node);
} else {
return node;
}
}
// Decompose a field node - wherever possible, merge the validity vectors of the
// child nodes.
Status ExprDecomposer::Visit(const FunctionNode& in_node) {
auto node = TryOptimize(in_node);
auto desc = node.descriptor();
FunctionSignature signature(desc->name(), desc->params(), desc->return_type());
const NativeFunction* native_function = registry_.LookupSignature(signature);
DCHECK(native_function) << "Missing Signature " << signature.ToString();
// decompose the children.
std::vector<ValueValidityPairPtr> args;
for (auto& child : node.children()) {
auto status = child->Accept(*this);
ARROW_RETURN_NOT_OK(status);
args.push_back(result());
}
// Make a function holder, if required.
std::shared_ptr<FunctionHolder> holder;
int holder_idx = -1;
if (native_function->NeedsFunctionHolder()) {
auto function_holder_maker_registry = registry_.GetFunctionHolderMakerRegistry();
ARROW_ASSIGN_OR_RAISE(holder,
function_holder_maker_registry.Make(desc->name(), node));
holder_idx = annotator_.AddHolderPointer(holder.get());
}
if (native_function->result_nullable_type() == kResultNullIfNull) {
// These functions are decomposable, merge the validity bits of the children.
std::vector<DexPtr> merged_validity;
for (auto& decomposed : args) {
// Merge the validity_expressions of the children to build a combined validity
// expression.
merged_validity.insert(merged_validity.end(), decomposed->validity_exprs().begin(),
decomposed->validity_exprs().end());
}
auto value_dex = std::make_shared<NonNullableFuncDex>(desc, native_function, holder,
holder_idx, args);
result_ = std::make_shared<ValueValidityPair>(merged_validity, value_dex);
} else if (native_function->result_nullable_type() == kResultNullNever) {
// These functions always output valid results. So, no validity dex.
auto value_dex = std::make_shared<NullableNeverFuncDex>(desc, native_function, holder,
holder_idx, args);
result_ = std::make_shared<ValueValidityPair>(value_dex);
} else {
DCHECK(native_function->result_nullable_type() == kResultNullInternal);
// Add a local bitmap to track the output validity.
int local_bitmap_idx = annotator_.AddLocalBitMap();
auto validity_dex = std::make_shared<LocalBitMapValidityDex>(local_bitmap_idx);
auto value_dex = std::make_shared<NullableInternalFuncDex>(
desc, native_function, holder, holder_idx, args, local_bitmap_idx);
result_ = std::make_shared<ValueValidityPair>(validity_dex, value_dex);
}
return Status::OK();
}
// Decompose an IfNode
Status ExprDecomposer::Visit(const IfNode& node) {
// nested_if_else_ might get overwritten when visiting the condition-node, so
// saving the value to a local variable and resetting nested_if_else_ to false
bool svd_nested_if_else = nested_if_else_;
nested_if_else_ = false;
PushConditionEntry(node);
auto status = node.condition()->Accept(*this);
ARROW_RETURN_NOT_OK(status);
auto condition_vv = result();
PopConditionEntry(node);
// Add a local bitmap to track the output validity.
int local_bitmap_idx = PushThenEntry(node, svd_nested_if_else);
status = node.then_node()->Accept(*this);
ARROW_RETURN_NOT_OK(status);
auto then_vv = result();
PopThenEntry(node);
PushElseEntry(node, local_bitmap_idx);
nested_if_else_ = (dynamic_cast<IfNode*>(node.else_node().get()) != nullptr);
status = node.else_node()->Accept(*this);
ARROW_RETURN_NOT_OK(status);
auto else_vv = result();
bool is_terminal_else = PopElseEntry(node);
auto validity_dex = std::make_shared<LocalBitMapValidityDex>(local_bitmap_idx);
auto value_dex =
std::make_shared<IfDex>(condition_vv, then_vv, else_vv, node.return_type(),
local_bitmap_idx, is_terminal_else);
result_ = std::make_shared<ValueValidityPair>(validity_dex, value_dex);
return Status::OK();
}
// Decompose a BooleanNode
Status ExprDecomposer::Visit(const BooleanNode& node) {
// decompose the children.
std::vector<ValueValidityPairPtr> args;
for (auto& child : node.children()) {
auto status = child->Accept(*this);
ARROW_RETURN_NOT_OK(status);
args.push_back(result());
}
// Add a local bitmap to track the output validity.
int local_bitmap_idx = annotator_.AddLocalBitMap();
auto validity_dex = std::make_shared<LocalBitMapValidityDex>(local_bitmap_idx);
std::shared_ptr<BooleanDex> value_dex;
switch (node.expr_type()) {
case BooleanNode::AND:
value_dex = std::make_shared<BooleanAndDex>(args, local_bitmap_idx);
break;
case BooleanNode::OR:
value_dex = std::make_shared<BooleanOrDex>(args, local_bitmap_idx);
break;
}
result_ = std::make_shared<ValueValidityPair>(validity_dex, value_dex);
return Status::OK();
}
Status ExprDecomposer::Visit(const InExpressionNode<gandiva::DecimalScalar128>& node) {
/* decompose the children. */
std::vector<ValueValidityPairPtr> args;
auto status = node.eval_expr()->Accept(*this);
ARROW_RETURN_NOT_OK(status);
args.push_back(result());
/* In always outputs valid results, so no validity dex */
auto value_dex = std::make_shared<InExprDex<gandiva::DecimalScalar128>>(
args, node.values(), node.get_precision(), node.get_scale());
int holder_idx = annotator_.AddHolderPointer(value_dex->in_holder().get());
value_dex->set_holder_idx(holder_idx);
result_ = std::make_shared<ValueValidityPair>(value_dex);
return Status::OK();
}
template <typename ctype>
Status ExprDecomposer::VisitInGeneric(const InExpressionNode<ctype>& node) {
/* decompose the children. */
std::vector<ValueValidityPairPtr> args;
auto status = node.eval_expr()->Accept(*this);
ARROW_RETURN_NOT_OK(status);
args.push_back(result());
/* In always outputs valid results, so no validity dex */
auto value_dex = std::make_shared<InExprDex<ctype>>(args, node.values());
int holder_idx = annotator_.AddHolderPointer(value_dex->in_holder().get());
value_dex->set_holder_idx(holder_idx);
result_ = std::make_shared<ValueValidityPair>(value_dex);
return Status::OK();
}
Status ExprDecomposer::Visit(const InExpressionNode<int32_t>& node) {
return VisitInGeneric<int32_t>(node);
}
Status ExprDecomposer::Visit(const InExpressionNode<int64_t>& node) {
return VisitInGeneric<int64_t>(node);
}
Status ExprDecomposer::Visit(const InExpressionNode<float>& node) {
return VisitInGeneric<float>(node);
}
Status ExprDecomposer::Visit(const InExpressionNode<double>& node) {
return VisitInGeneric<double>(node);
}
Status ExprDecomposer::Visit(const InExpressionNode<std::string>& node) {
return VisitInGeneric<std::string>(node);
}
Status ExprDecomposer::Visit(const LiteralNode& node) {
auto value_dex = std::make_shared<LiteralDex>(node.return_type(), node.holder());
DexPtr validity_dex;
if (node.is_null()) {
validity_dex = std::make_shared<FalseDex>();
} else {
validity_dex = std::make_shared<TrueDex>();
}
result_ = std::make_shared<ValueValidityPair>(validity_dex, value_dex);
return Status::OK();
}
// The below functions use a stack to detect :
// a. nested if-else expressions.
// In such cases, the local bitmap can be re-used.
// b. detect terminal else expressions
// The non-terminal else expressions do not need to track validity (the if statement
// that has a match will do it).
// Both of the above optimisations save CPU cycles during expression evaluation.
int ExprDecomposer::PushThenEntry(const IfNode& node, bool reuse_bitmap) {
int local_bitmap_idx;
if (reuse_bitmap) {
// we also need stack in addition to reuse_bitmap flag since we
// can also enter other if-else nodes when we visit the condition-node
// (which themselves might be nested) before we visit then-node
DCHECK_EQ(if_entries_stack_.empty(), false) << "PushThenEntry: stack is empty";
DCHECK_EQ(if_entries_stack_.top()->entry_type_, kStackEntryElse)
<< "PushThenEntry: top of stack is not of type entry_else";
auto top = if_entries_stack_.top().get();
// inside a nested else statement (i.e if-else-if). use the parent's bitmap.
local_bitmap_idx = top->local_bitmap_idx_;
// clear the is_terminal bit in the current top entry (else).
top->is_terminal_else_ = false;
} else {
// alloc a new bitmap.
local_bitmap_idx = annotator_.AddLocalBitMap();
}
// push new entry to the stack.
std::unique_ptr<IfStackEntry> entry(new IfStackEntry(
node, kStackEntryThen, false /*is_terminal_else*/, local_bitmap_idx));
if_entries_stack_.emplace(std::move(entry));
return local_bitmap_idx;
}
void ExprDecomposer::PopThenEntry(const IfNode& node) {
DCHECK_EQ(if_entries_stack_.empty(), false) << "PopThenEntry: found empty stack";
auto top = if_entries_stack_.top().get();
DCHECK_EQ(top->entry_type_, kStackEntryThen)
<< "PopThenEntry: found " << top->entry_type_ << " expected then";
DCHECK_EQ(&top->if_node_, &node) << "PopThenEntry: found mismatched node";
if_entries_stack_.pop();
}
void ExprDecomposer::PushElseEntry(const IfNode& node, int local_bitmap_idx) {
std::unique_ptr<IfStackEntry> entry(new IfStackEntry(
node, kStackEntryElse, true /*is_terminal_else*/, local_bitmap_idx));
if_entries_stack_.emplace(std::move(entry));
}
bool ExprDecomposer::PopElseEntry(const IfNode& node) {
DCHECK_EQ(if_entries_stack_.empty(), false) << "PopElseEntry: found empty stack";
auto top = if_entries_stack_.top().get();
DCHECK_EQ(top->entry_type_, kStackEntryElse)
<< "PopElseEntry: found " << top->entry_type_ << " expected else";
DCHECK_EQ(&top->if_node_, &node) << "PopElseEntry: found mismatched node";
bool is_terminal_else = top->is_terminal_else_;
if_entries_stack_.pop();
return is_terminal_else;
}
void ExprDecomposer::PushConditionEntry(const IfNode& node) {
std::unique_ptr<IfStackEntry> entry(new IfStackEntry(node, kStackEntryCondition));
if_entries_stack_.emplace(std::move(entry));
}
void ExprDecomposer::PopConditionEntry(const IfNode& node) {
DCHECK_EQ(if_entries_stack_.empty(), false) << "PopConditionEntry: found empty stack";
auto top = if_entries_stack_.top().get();
DCHECK_EQ(top->entry_type_, kStackEntryCondition)
<< "PopConditionEntry: found " << top->entry_type_ << " expected condition";
DCHECK_EQ(&top->if_node_, &node) << "PopConditionEntry: found mismatched node";
if_entries_stack_.pop();
}
} // namespace gandiva