blob: 3b5d5d423a8a096ef1e10f528c5e62aadfd03bc7 [file]
// 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 "codegen/llvm-codegen.h"
#include "exec/cte-consumer-node.h"
#include "exec/exec-node-util.h"
#include "exprs/scalar-expr-evaluator.h"
#include "exprs/scalar-expr.h"
#include "runtime/exec-env.h"
#include "runtime/fragment-state.h"
#include "runtime/local-exchanger.h"
#include "runtime/row-batch.h"
#include "runtime/runtime-state.h"
#include "runtime/tuple.h"
#include "util/debug-util.h"
#include "util/runtime-profile-counters.h"
#include "util/runtime-profile.h"
#include "common/names.h"
namespace impala {
Status CTEConsumerPlanNode::Init(const TPlanNode& tnode, FragmentState* state) {
RETURN_IF_ERROR(PlanNode::Init(tnode, state));
DCHECK(tnode_->__isset.cte_consumer);
const TCTEConsumer& cte_node = tnode_->cte_consumer;
DCHECK_EQ(row_descriptor_->tuple_descriptors().size(), 1);
tuple_desc_ = row_descriptor_->tuple_descriptors()[0];
DCHECK_EQ(cte_node.input_row_tuples.size(), cte_node.nullable_tuples.size());
RowDescriptor input_row_desc(
state->desc_tbl(), cte_node.input_row_tuples, cte_node.nullable_tuples);
RETURN_IF_ERROR(
ScalarExpr::Create(cte_node.result_exprs, input_row_desc, state, &input_exprs_));
DCHECK_EQ(input_exprs_.size(), tuple_desc_->slots().size());
is_passthrough_ = row_descriptor_->LayoutEquals(input_row_desc);
return Status::OK();
}
void CTEConsumerPlanNode::Close() {
ScalarExpr::Close(input_exprs_);
PlanNode::Close();
}
Status CTEConsumerPlanNode::CreateExecNode(RuntimeState* state, ExecNode** node) const {
ObjectPool* pool = state->obj_pool();
*node = pool->Add(new CTEConsumerNode(pool, *this, state->desc_tbl()));
return Status::OK();
}
void CTEConsumerPlanNode::Codegen(FragmentState* state) {
DCHECK(state->ShouldCodegen());
PlanNode::Codegen(state);
if (IsNodeCodegenDisabled()) return;
if (is_passthrough_) return;
LlvmCodeGen* codegen = state->codegen();
DCHECK(codegen != nullptr);
std::stringstream codegen_message;
llvm::Function* tuple_materialize_exprs_fn;
Status codegen_status = Tuple::CodegenMaterializeExprs(codegen, false, *tuple_desc_,
input_exprs_, true, &tuple_materialize_exprs_fn);
if (!codegen_status.ok()) {
// Codegen may fail in some corner cases. If this happens, abort codegen.
AddCodegenStatus(codegen_status, "Codegen failed");
return;
}
// Get a copy of the function. This function will be modified and added to the
// vector of functions.
llvm::Function* materialize_batch_fn =
codegen->GetFunction(IRFunction::CTE_MATERIALIZE_BATCH, true);
DCHECK(materialize_batch_fn != nullptr);
int replaced = codegen->ReplaceCallSites(materialize_batch_fn,
tuple_materialize_exprs_fn, Tuple::MATERIALIZE_EXPRS_SYMBOL);
DCHECK_REPLACE_COUNT(replaced, 1) << LlvmCodeGen::Print(materialize_batch_fn);
materialize_batch_fn = codegen->FinalizeFunction(materialize_batch_fn);
DCHECK(materialize_batch_fn != nullptr);
// Add the function to Jit and to the vector of codegened functions.
codegen->AddFunctionToJit(materialize_batch_fn, &codegend_materialize_batch_fn_);
}
CTEConsumerNode::CTEConsumerNode(
ObjectPool* pool, const CTEConsumerPlanNode& pnode, const DescriptorTbl& descs)
: ExecNode(pool, pnode, descs),
tuple_desc_(pnode.tuple_desc_),
input_exprs_(pnode.input_exprs_),
codegend_materialize_batch_fn_(pnode.codegend_materialize_batch_fn_),
is_passthrough_(pnode.is_passthrough_) { }
Status CTEConsumerNode::Prepare(RuntimeState* state) {
RETURN_IF_ERROR(ExecNode::Prepare(state));
if (!is_passthrough_) {
RETURN_IF_ERROR(ScalarExprEvaluator::Create(input_exprs_, state, pool_,
expr_perm_pool(), expr_results_pool(), &input_expr_evals_));
}
// Must match CTEProducerPlanNode::GetCTEName. CTEProducerPlanNode::Init registers CTEs
// before fragment instances begin execution, so the mapping must exist here.
if (auto it = state->instance_ctx().cte_consumer_to_producer_idx.find(id());
it != state->instance_ctx().cte_consumer_to_producer_idx.end()) {
name_ = plan_node().tnode_->cte_consumer.name + "_" + std::to_string(it->second);
} else {
// No CTE producer scheduled on this node for this consumer. Already logged in
// Scheduler::ComputeFragmentExecParams.
name_ = plan_node().tnode_->cte_consumer.name;
}
return Status::OK();
}
Status CTEConsumerNode::Open(RuntimeState* state) {
SCOPED_TIMER(runtime_profile()->total_time_counter());
ScopedOpenEventAdder ea(this);
RETURN_IF_ERROR(ExecNode::Open(state));
if (!buffer_pool_client()->is_registered()) {
RETURN_IF_ERROR(ClaimBufferReservation(state));
}
if (!is_passthrough_) {
RETURN_IF_ERROR(ScalarExprEvaluator::Open(input_expr_evals_, state));
}
DCHECK_EQ(nullptr, exchanger_);
VLOG_QUERY << "Finding CTE exchange " << name_ << " in instance "
<< state->instance_ctx().per_fragment_instance_idx << " of " << label();
exchanger_ = state->query_state()->GetExchanger(name_);
if (exchanger_ != nullptr) {
consumer_index_ = exchanger_->Open();
} else {
VLOG_QUERY << "No CTE exchanger present for CTE consumer: " << name_;
}
return Status::OK();
}
Status CTEConsumerNode::GetNext(
RuntimeState* state, RowBatch* output_batch, bool* eos) {
SCOPED_TIMER(runtime_profile()->total_time_counter());
ScopedGetNextEventAdder ea(this, eos);
RETURN_IF_ERROR(ExecDebugAction(TExecNodePhase::GETNEXT, state));
RETURN_IF_CANCELLED(state);
RETURN_IF_ERROR(QueryMaintenance(state));
if (exchanger_ == nullptr) {
*eos = true;
return Status::OK();
}
// Fetch rows from LocalExchanger
RowBatch* input_batch = exchanger_->Pull(consumer_index_, eos);
if (input_batch == nullptr) {
// Pull only returns nullptr when eos_ is set and no batches remain.
DCHECK(*eos);
return Status::OK();
}
VLOG_PROGRESS << "Pulled " << input_batch->num_rows() << " rows from CTE exchange "
<< name_ << " in instance " << state->instance_ctx().per_fragment_instance_idx
<< " of " << label();
if (is_passthrough_) {
int rows_to_copy = input_batch->num_rows();
if (rows_to_copy > 0) {
DCHECK_LE(rows_to_copy, output_batch->capacity() - output_batch->num_rows())
<< "Output batch capacity: " << output_batch->capacity()
<< ", current rows: " << output_batch->num_rows()
<< ", input batch rows: " << input_batch->num_rows();
int dst_offset = output_batch->AddRows(rows_to_copy);
output_batch->CopyRows(input_batch, rows_to_copy, 0, dst_offset);
output_batch->CommitRows(rows_to_copy);
// Ensure blocking operators make a deep copy of data if they need to retain it.
// Heap memory is re-used from input_batch; if this is the last reference to the
// Cell in LocalExchanger, it will be freed on the next GetNext() call.
output_batch->MarkNeedsDeepCopy();
}
} else {
// Copy input_batch to output_batch while translating slots.
int64_t tuple_buf_size;
uint8_t* tuple_buf;
RETURN_IF_ERROR(output_batch->ResizeAndAllocateTupleBuffer(
state, &tuple_buf_size, &tuple_buf));
memset(tuple_buf, 0, tuple_buf_size);
CTEConsumerPlanNode::MaterializeBatchFn fn = codegend_materialize_batch_fn_.load();
if (fn == nullptr) {
MaterializeBatch(input_batch, output_batch, &tuple_buf);
} else {
fn(this, input_batch, output_batch, &tuple_buf);
}
}
CheckLimitAndTruncateRowBatchIfNeeded(output_batch, eos);
COUNTER_SET(rows_returned_counter_, rows_returned());
return Status::OK();
}
Status CTEConsumerNode::Reset(RuntimeState* state, RowBatch* row_batch) {
// Reset() is not supported.
const char* msg = "Internal error: CTE consumer nodes should not appear in subplans.";
DCHECK(false) << msg;
return Status(msg);
}
void CTEConsumerNode::Close(RuntimeState* state) {
if (is_closed()) return;
if (exchanger_) {
VLOG_QUERY << "Closing consumer of CTE exchange " << name_ << " in instance "
<< state->instance_ctx().per_fragment_instance_idx << " of " << label();
exchanger_->CloseConsumer(consumer_index_);
}
if (!is_passthrough_) {
ScalarExprEvaluator::Close(input_expr_evals_, state);
}
ExecNode::Close(state);
}
void CTEConsumerNode::DebugString(int indentation_level, stringstream* out) const {
*out << string(indentation_level * 2, ' ') << "CTEConsumerNode(" << name_;
ExecNode::DebugString(indentation_level, out);
*out << ")";
}
}