github-actions[bot] commented on code in PR #65369:
URL: https://github.com/apache/doris/pull/65369#discussion_r3555935435
##########
be/src/format_v2/parquet/parquet_scan.cpp:
##########
@@ -597,42 +786,517 @@ Status
ParquetScanScheduler::skip_current_row_group_rows(int64_t rows) {
return Status::OK();
}
+namespace {
+
+struct PredicateConjunctSchedule {
+ std::map<size_t, VExprContextSPtrs> single_column_conjuncts;
+ VExprContextSPtrs remaining_conjuncts;
+};
+
+PredicateConjunctSchedule build_predicate_conjunct_schedule(
+ const format::FileScanRequest& request) {
+ std::unordered_set<size_t> predicate_block_positions;
+ predicate_block_positions.reserve(request.predicate_columns.size());
+ for (const auto& col : request.predicate_columns) {
+ const auto position_it = request.local_positions.find(col.column_id());
+ DORIS_CHECK(position_it != request.local_positions.end());
+ predicate_block_positions.insert(position_it->second.value());
+ }
+
+ PredicateConjunctSchedule schedule;
+ for (const auto& conjunct : request.conjuncts) {
+ DORIS_CHECK(conjunct != nullptr);
+ DORIS_CHECK(conjunct->root() != nullptr);
+ if (!conjunct->root()->is_safe_to_execute_on_selected_rows()) {
Review Comment:
This safety gate only inspects `request.conjuncts`, but delete predicates
can also be error-preserving. Iceberg equality deletes populate
`request.delete_conjuncts` separately, and when a data key type differs from
the delete-key type `_append_equality_delete_predicates()` wraps the slot in
`format::Cast`; that cast propagates `_function->execute()` errors. With a
normal predicate such as `a > 0` enabling the round-by-round path, rows
rejected by `a` can be compacted away before
`execute_scheduled_delete_conjuncts()` runs, so a bad equality-delete cast on
one of those rows is suppressed. The old batch path kept the full `batch_rows`
block for delete conjunct evaluation after regular filters updated only the
selection vector. Please include delete conjunct roots in the selected-row
safety decision, or force the full-batch path whenever a delete predicate is
not proven safe on compacted rows.
##########
be/src/format_v2/parquet/reader/scalar_column_reader.cpp:
##########
@@ -215,6 +251,174 @@ Status ScalarColumnReader::skip(int64_t rows) {
return Status::OK();
}
+Status ScalarColumnReader::select_with_dictionary_filter(const
SelectionVector& sel,
+ uint16_t
selected_rows, int64_t batch_rows,
+ const
IColumn::Filter& dictionary_filter,
+ MutableColumnPtr&
column,
+ IColumn::Filter*
row_filter,
+ bool* used_filter) {
+ DORIS_CHECK(column.get() != nullptr);
+ DORIS_CHECK(row_filter != nullptr);
+ DORIS_CHECK(used_filter != nullptr);
+ RETURN_IF_ERROR(sel.verify(selected_rows, batch_rows));
+ *used_filter = false;
+ row_filter->clear();
+ row_filter->reserve(selected_rows);
+
+ const auto ranges = selection_to_ranges(sel, selected_rows);
+ int64_t cursor = 0;
+ for (const auto& range : ranges) {
+ if (range.start < cursor || range.start + range.length > batch_rows) {
+ return Status::InvalidArgument(
+ "Invalid parquet dictionary selection range [{}, {}) for
column {}",
+ range.start, range.start + range.length, _name);
+ }
+ RETURN_IF_ERROR(skip(range.start - cursor));
+
+ int64_t range_rows_read = 0;
+ RETURN_IF_ERROR(read_range_with_dictionary_filter(range.length,
dictionary_filter, column,
+ row_filter,
&range_rows_read,
+ used_filter));
+ if (!*used_filter) {
+ return Status::OK();
+ }
+ if (range_rows_read != range.length) {
+ return Status::Corruption(
+ "Parquet dictionary selected read returned {} rows,
expected {} rows for "
+ "column {}",
+ range_rows_read, range.length, _name);
+ }
+ cursor = range.start + range.length;
+ }
+ RETURN_IF_ERROR(skip(batch_rows - cursor));
+ if (_profile.reader_select_rows != nullptr) {
+ COUNTER_UPDATE(_profile.reader_select_rows, selected_rows);
+ }
+ return Status::OK();
+}
+
+Status ScalarColumnReader::read_range_with_dictionary_filter(
+ int64_t rows, const IColumn::Filter& dictionary_filter,
MutableColumnPtr& column,
+ IColumn::Filter* row_filter, int64_t* rows_read, bool* used_filter) {
+ DORIS_CHECK(row_filter != nullptr);
+ DORIS_CHECK(rows_read != nullptr);
+ DORIS_CHECK(used_filter != nullptr);
+ DORIS_CHECK(_record_reader != nullptr);
+ if (!_record_reader->read_dictionary()) {
+ *used_filter = false;
+ return Status::OK();
+ }
+
+ ParquetLeafBatch leaf_batch;
+ RETURN_IF_ERROR(leaf_reader().read_batch(rows, &leaf_batch, rows_read));
+ int64_t matched_rows = 0;
+
RETURN_IF_ERROR(append_dictionary_filtered_values(leaf_batch.binary_chunks(),
dictionary_filter,
+ column, row_filter,
&matched_rows,
+ used_filter));
+ if (!*used_filter) {
+ return Status::Corruption(
+ "Parquet dictionary reader did not return dictionary batches
for column {}", _name);
+ }
+ if (row_filter->size() < static_cast<size_t>(*rows_read)) {
+ return Status::Corruption(
+ "Parquet dictionary filter produced too few row decisions for
column {}: "
+ "filter={}, rows={}",
+ _name, row_filter->size(), *rows_read);
+ }
+ advance_rows_read(*rows_read);
+ update_reader_read_rows(*rows_read);
+ return Status::OK();
+}
+
+Status ScalarColumnReader::append_dictionary_filtered_values(
+ const std::vector<std::shared_ptr<::arrow::Array>>& chunks,
+ const IColumn::Filter& dictionary_filter, MutableColumnPtr& column,
+ IColumn::Filter* row_filter, int64_t* matched_rows, bool* used_filter)
const {
+ DORIS_CHECK(row_filter != nullptr);
+ DORIS_CHECK(matched_rows != nullptr);
+ DORIS_CHECK(used_filter != nullptr);
+ *matched_rows = 0;
+ *used_filter = false;
+
+ std::vector<StringRef> selected_values;
+ for (const auto& chunk : chunks) {
+ DORIS_CHECK(chunk != nullptr);
+ const auto* dict_array = dynamic_cast<const
::arrow::DictionaryArray*>(chunk.get());
+ if (dict_array == nullptr) {
+ // The caller has already consumed rows from a
DictionaryRecordReader. Falling back to a
+ // normal selected read would desynchronize the Parquet stream, so
absence of a
+ // DictionaryArray is reported as corruption by
read_range_with_dictionary_filter().
+ return Status::OK();
+ }
+ *used_filter = true;
+ const auto& dictionary = dict_array->dictionary();
+ if (dictionary == nullptr) {
+ return Status::Corruption("Parquet dictionary array has null
dictionary for column {}",
+ _name);
+ }
+
+ // Dictionary predicates are evaluated once against the dictionary
page and produce a
+ // dictionary-entry bitmap. DATA_PAGE rows then only need an
integer-index lookup. NULL rows
+ // do not have a dictionary entry and cannot satisfy the supported
equality/IN predicates.
+ for (int64_t row = 0; row < dict_array->length(); ++row) {
+ bool keep = false;
+ if (!dict_array->IsNull(row)) {
+ const int64_t dictionary_index =
dict_array->GetValueIndex(row);
+ if (dictionary_index >= 0 &&
+ dictionary_index <
static_cast<int64_t>(dictionary_filter.size())) {
+ keep =
dictionary_filter[static_cast<size_t>(dictionary_index)] != 0;
+ }
+ if (keep) {
+ RETURN_IF_ERROR(append_arrow_binary_dictionary_value(
+ _name, *dictionary, dictionary_index,
&selected_values));
+ ++*matched_rows;
+ }
+ }
+ row_filter->push_back(keep ? 1 : 0);
+ }
+ }
+
+ if (!*used_filter) {
+ return Status::OK();
+ }
+ return append_decoded_binary_values(selected_values, column);
+}
+
+Status ScalarColumnReader::append_decoded_binary_values(const
std::vector<StringRef>& values,
+ MutableColumnPtr&
column) const {
+ DecodedColumnView view;
+ view.value_kind = decoded_value_kind(_type_descriptor);
+ view.row_count = static_cast<int64_t>(values.size());
+ view.logical_integer_bit_width = _type_descriptor.integer_bit_width;
+ view.logical_integer_is_signed = !_type_descriptor.is_unsigned_integer;
+ view.fixed_length = _type_descriptor.fixed_length;
+ view.binary_values = &values;
+
+ SCOPED_TIMER(_profile.materialization_time);
+ if (!_type->is_nullable()) {
+ if (auto* nullable_column =
check_and_get_column<ColumnNullable>(*column);
+ nullable_column != nullptr) {
+ auto& nested_column = nullable_column->get_nested_column();
+ auto& null_map = nullable_column->get_null_map_data();
+ const auto old_nested_size = nested_column.size();
+ const auto old_null_map_size = null_map.size();
+ auto st =
_type->get_serde()->read_column_from_decoded_values(nested_column, view);
+ if (!st.ok()) {
+ nested_column.resize(old_nested_size);
+ return st;
+ }
+ null_map.resize(old_null_map_size + nested_column.size() -
old_nested_size);
+ memset(null_map.data() + old_null_map_size, 0, null_map.size() -
old_null_map_size);
Review Comment:
The new `append_decoded_binary_values()` helper uses `memset` here, but this
translation unit does not include `<cstring>` directly. It may compile today
through an Arrow/Parquet transitive include, but the declaration is not
guaranteed by the headers this file owns. Please add `#include <cstring>` near
the other standard headers so this file is not include-order dependent.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]