| 636 | } |
| 637 | |
| 638 | Status AnalyticEvalNode::ProcessChildBatch(RuntimeState* state) { |
| 639 | // TODO: DCHECK input is sorted (even just first row vs prev_input_tuple_) |
| 640 | VLOG_FILE << id() << " ProcessChildBatch: " << DebugStateString() |
| 641 | << " input batch size:" << curr_child_batch_->num_rows() |
| 642 | << " tuple pool size:" << curr_tuple_pool_->total_allocated_bytes(); |
| 643 | SCOPED_TIMER(evaluation_timer_); |
| 644 | |
| 645 | // BufferedTupleStream::num_rows() returns the total number of rows that have been |
| 646 | // inserted into the stream (it does not decrease when we read rows), so the index of |
| 647 | // the next input row that will be inserted will be the current size of the stream. |
| 648 | int64_t stream_idx = input_stream_->num_rows(); |
| 649 | |
| 650 | // The very first row in the stream is handled specially because there is no previous |
| 651 | // row to compare and we cannot rely on PrevRowCompare() returning true even for the |
| 652 | // same row pointers if there are NaN values. |
| 653 | int batch_idx = 0; |
| 654 | if (UNLIKELY(stream_idx == 0 && curr_child_batch_->num_rows() > 0)) { |
| 655 | TupleRow* row = curr_child_batch_->GetRow(0); |
| 656 | RETURN_IF_ERROR(AddRow(0, row)); |
| 657 | RETURN_IF_ERROR(TryAddResultTupleForCurrRow(0)); |
| 658 | prev_input_tuple_ = row->GetTuple(0); |
| 659 | ++batch_idx; |
| 660 | ++stream_idx; |
| 661 | } |
| 662 | Tuple* child_tuple_cmp_row_tuples[2] = {nullptr, nullptr}; |
| 663 | TupleRow* child_tuple_cmp_row = |
| 664 | reinterpret_cast<TupleRow*>(child_tuple_cmp_row_tuples); |
| 665 | for (; batch_idx < curr_child_batch_->num_rows(); ++batch_idx, ++stream_idx) { |
| 666 | TupleRow* row = curr_child_batch_->GetRow(batch_idx); |
| 667 | if (has_partition_or_order_by_expr_eval()) { |
| 668 | // Only set the tuples in child_tuple_cmp_row if there are partition exprs or |
| 669 | // order by exprs that require comparing the current and previous rows. If there |
| 670 | // aren't partition or order by exprs (i.e. empty OVER() clause), there was no |
| 671 | // sort and there could be nullable tuples (whereas the sort node does not produce |
| 672 | // them), see IMPALA-1562. |
| 673 | child_tuple_cmp_row->SetTuple(0, prev_input_tuple_); |
| 674 | child_tuple_cmp_row->SetTuple(1, row->GetTuple(0)); |
| 675 | } |
| 676 | TryRemoveRowsBeforeWindow(stream_idx); |
| 677 | |
| 678 | // Every row is compared against the previous row to determine if (a) the row |
| 679 | // starts a new partition or (b) the row does not share the same values for the |
| 680 | // ordering exprs. When either of these occurs, the analytic_fn_evals_ are |
| 681 | // finalized and the result tuple is added to result_tuples_ so that it may be |
| 682 | // added to output rows in GetNextOutputBatch(). When a new partition is found |
| 683 | // (a), a new, empty result tuple is created and initialized over the |
| 684 | // analytic_fn_evals_. If the row has different values for the ordering |
| 685 | // exprs (b), then a new tuple is created but copied from curr_tuple_ because the |
| 686 | // original is used for one or more previous row(s) but the incremental state still |
| 687 | // applies to the current row. |
| 688 | bool next_partition = false; |
| 689 | if (partition_by_eq_expr_eval_ != nullptr) { |
| 690 | // partition_by_eq_expr_eval_ checks equality over the predicate exprs |
| 691 | next_partition = !PrevRowCompare(partition_by_eq_expr_eval_, child_tuple_cmp_row); |
| 692 | } |
| 693 | RETURN_IF_ERROR(TryAddResultTupleForPrevRow( |
| 694 | child_tuple_cmp_row, next_partition, stream_idx)); |
| 695 | if (next_partition) RETURN_IF_ERROR(InitNextPartition(state, stream_idx)); |
nothing calls this directly
no test coverage detected