This is an automated email from the ASF dual-hosted git repository.
morningman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new 5b3ac63f8b4 [improvement](parquet) Make V2 column initialization lazy
(#66073)
5b3ac63f8b4 is described below
commit 5b3ac63f8b49018265f7a80db9e2b86db67c4cbb
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Mon Jul 27 10:16:28 2026 +0800
[improvement](parquet) Make V2 column initialization lazy (#66073)
## Proposed changes
Copy of #66073, close #66073
- make File Scanner V2 Parquet column chunk initialization perform no
page I/O
- lazily detect and decode dictionary pages on the first operation that
needs page state
- preserve dictionary initialization before sequential advances and
OffsetIndex seeks
- add regression coverage for zero-I/O initialization, dictionary
probing, and indexed page skipping
## Test
- `./run-be-ut.sh --run --filter=ParquetV2NativeDecoderTest.* -j 24`
(106 tests passed)
- `build-support/check-format.sh`
- changed-line `clang-tidy`
---------
Co-authored-by: Gabriel <[email protected]>
---
.../parquet/reader/native/column_chunk_reader.cpp | 60 ++-
.../parquet/reader/native/column_chunk_reader.h | 7 +-
.../parquet/reader/native/column_reader.cpp | 25 +-
.../iceberg_position_delete_sys_table_reader.cpp | 10 +-
be/src/runtime/runtime_profile.cpp | 17 +
be/src/runtime/runtime_profile.h | 5 +
be/test/format_v2/parquet/native_decoder_test.cpp | 506 ++++++++++++++++++++-
be/test/runtime/runtime_profile_test.cpp | 31 ++
8 files changed, 624 insertions(+), 37 deletions(-)
diff --git a/be/src/format_v2/parquet/reader/native/column_chunk_reader.cpp
b/be/src/format_v2/parquet/reader/native/column_chunk_reader.cpp
index 0d59b7e6bc2..b7bea8e7053 100644
--- a/be/src/format_v2/parquet/reader/native/column_chunk_reader.cpp
+++ b/be/src/format_v2/parquet/reader/native/column_chunk_reader.cpp
@@ -844,7 +844,6 @@ Status ColumnChunkReader<IN_COLLECTION,
OFFSET_INDEX>::init() {
// get the block compression codec
RETURN_IF_ERROR(get_block_compression_codec(_metadata.codec,
&_block_compress_codec));
_state = INITIALIZED;
- RETURN_IF_ERROR(_parse_first_page_header());
return Status::OK();
}
@@ -908,30 +907,56 @@ Status ColumnChunkReader<IN_COLLECTION,
OFFSET_INDEX>::read_levels(
}
template <bool IN_COLLECTION, bool OFFSET_INDEX>
-Status ColumnChunkReader<IN_COLLECTION,
OFFSET_INDEX>::_parse_first_page_header() {
+Status ColumnChunkReader<IN_COLLECTION,
OFFSET_INDEX>::_ensure_dictionary_page_loaded() {
+ if (_dict_checked) {
+ return Status::OK();
+ }
+
+ DORIS_CHECK(_state == INITIALIZED);
while (true) {
RETURN_IF_ERROR(_page_reader->parse_page_header());
const tparquet::PageHeader* header = nullptr;
RETURN_IF_ERROR(_page_reader->get_page_header(&header));
if (header->type == tparquet::PageType::DATA_PAGE ||
header->type == tparquet::PageType::DATA_PAGE_V2) {
- _state = INITIALIZED;
- return parse_page_header();
+ if constexpr (IN_COLLECTION && OFFSET_INDEX) {
+ if (header->type == tparquet::PageType::DATA_PAGE &&
+ _page_reader->has_active_offset_index()) {
+ // V1 nested pages expose row boundaries only in
repetition levels, so an
+ // indexed seek must not skip the first page before those
levels are decoded.
+ _page_reader->discard_offset_index();
+ _offset_index = nullptr;
+ }
+ }
+ _dict_checked = true;
+ return Status::OK();
}
if (header->type != tparquet::PageType::DICTIONARY_PAGE) {
RETURN_IF_ERROR(_page_reader->skip_auxiliary_page());
- _state = INITIALIZED;
continue;
}
- // the first page maybe directory page even if
_metadata.__isset.dictionary_page_offset == false,
- // so we should parse the directory page in next_page()
RETURN_IF_ERROR(_decode_dict_page());
- // parse the real first data page
RETURN_IF_ERROR(_page_reader->dict_next_page());
- _state = INITIALIZED;
- // A dictionary is the only non-data page with decoder state. Any
following index or
- // extension pages are skipped by the same pre-data loop.
+ // A nested V1 chunk must inspect its first data-page type before
indexed seeking can skip
+ // that page; dictionary discovery alone is not enough to make the
OffsetIndex trustworthy.
+ }
+}
+
+template <bool IN_COLLECTION, bool OFFSET_INDEX>
+Status ColumnChunkReader<IN_COLLECTION,
OFFSET_INDEX>::load_dictionary_page(bool* has_dict) {
+ RETURN_IF_ERROR(_ensure_dictionary_page_loaded());
+ *has_dict = _has_dict;
+ return Status::OK();
+}
+
+template <bool IN_COLLECTION, bool OFFSET_INDEX>
+Status ColumnChunkReader<IN_COLLECTION,
OFFSET_INDEX>::ensure_first_data_page_parsed() {
+ if (_first_data_page_parsed) {
+ return Status::OK();
}
+ // OffsetIndex row bounds are untrusted until page zero has been
reconciled and its declared
+ // cardinality checked, so no indexed skip may observe them before this
one-time parse.
+ return parse_page_header();
}
template <bool IN_COLLECTION, bool OFFSET_INDEX>
@@ -939,6 +964,7 @@ Status ColumnChunkReader<IN_COLLECTION,
OFFSET_INDEX>::parse_page_header() {
if (_state == HEADER_PARSED || _state == DATA_LOADED) {
return Status::OK();
}
+ RETURN_IF_ERROR(_ensure_dictionary_page_loaded());
const tparquet::PageHeader* header = nullptr;
while (true) {
RETURN_IF_ERROR(_page_reader->parse_page_header());
@@ -1004,12 +1030,19 @@ Status ColumnChunkReader<IN_COLLECTION,
OFFSET_INDEX>::parse_page_header() {
if (!active_offset_index) {
_chunk_parsed_values += _remaining_num_values;
}
+ _first_data_page_parsed = true;
_state = HEADER_PARSED;
return Status::OK();
}
template <bool IN_COLLECTION, bool OFFSET_INDEX>
Status ColumnChunkReader<IN_COLLECTION, OFFSET_INDEX>::next_page() {
+ if constexpr (OFFSET_INDEX) {
+ RETURN_IF_ERROR(ensure_first_data_page_parsed());
+ } else {
+ // Load dictionary state before advancing can jump past the physical
dictionary page.
+ RETURN_IF_ERROR(_ensure_dictionary_page_loaded());
+ }
// Level parsing advances _page_data past the allocation base, so retain
explicit ownership
// state instead of inferring whether current decoders still reference
decompressed storage.
_page_uses_decompress_buf = false;
@@ -1022,8 +1055,8 @@ Status ColumnChunkReader<IN_COLLECTION,
OFFSET_INDEX>::next_page() {
_decompress_release_pending = false;
_decompress_release_threshold = std::numeric_limits<size_t>::max();
}
- _state = INITIALIZED;
RETURN_IF_ERROR(_page_reader->next_page());
+ _state = INITIALIZED;
return Status::OK();
}
@@ -1909,6 +1942,9 @@ Status ColumnChunkReader<IN_COLLECTION,
OFFSET_INDEX>::filter_dictionary_indices
template <bool IN_COLLECTION, bool OFFSET_INDEX>
Status ColumnChunkReader<IN_COLLECTION,
OFFSET_INDEX>::seek_to_nested_row(size_t left_row) {
+ if constexpr (IN_COLLECTION && OFFSET_INDEX) {
+ RETURN_IF_ERROR(ensure_first_data_page_parsed());
+ }
if constexpr (OFFSET_INDEX) {
if (_page_reader->has_active_offset_index()) {
while (true) {
diff --git a/be/src/format_v2/parquet/reader/native/column_chunk_reader.h
b/be/src/format_v2/parquet/reader/native/column_chunk_reader.h
index 4a6dbecd4c5..5b6a7ad8be8 100644
--- a/be/src/format_v2/parquet/reader/native/column_chunk_reader.h
+++ b/be/src/format_v2/parquet/reader/native/column_chunk_reader.h
@@ -220,7 +220,7 @@ public:
level_t max_rep_level() const { return _max_rep_level; }
level_t max_def_level() const { return _max_def_level; }
- bool has_dict() const { return _has_dict; };
+ Status load_dictionary_page(bool* has_dict);
// Get page decoder
Decoder* get_page_decoder() { return _page_decoder; }
@@ -306,6 +306,7 @@ public:
size_t page_end_row() const { return _page_reader->end_row(); }
+ Status ensure_first_data_page_parsed();
Status parse_page_header();
Status next_page();
@@ -346,8 +347,7 @@ public:
private:
enum ColumnChunkReaderState { NOT_INIT, INITIALIZED, HEADER_PARSED,
DATA_LOADED, PAGE_SKIPPED };
- // for check dict page.
- Status _parse_first_page_header();
+ Status _ensure_dictionary_page_loaded();
Status _decode_dict_page();
void _reserve_decompress_buf(size_t size);
@@ -410,6 +410,7 @@ private:
Slice _v2_rep_levels;
Slice _v2_def_levels;
bool _dict_checked = false;
+ bool _first_data_page_parsed = false;
bool _has_dict = false;
bool _nested_row_started = false;
Decoder* _page_decoder = nullptr;
diff --git a/be/src/format_v2/parquet/reader/native/column_reader.cpp
b/be/src/format_v2/parquet/reader/native/column_reader.cpp
index 3f06d92dc9a..bffad92baf2 100644
--- a/be/src/format_v2/parquet/reader/native/column_reader.cpp
+++ b/be/src/format_v2/parquet/reader/native/column_reader.cpp
@@ -1168,10 +1168,10 @@ Status ScalarColumnReader<IN_COLLECTION,
OFFSET_INDEX>::read_fixed_width_filter(
int64_t right_row = 0;
if constexpr (OFFSET_INDEX == false) {
RETURN_IF_ERROR(_chunk_reader->parse_page_header());
- right_row = _chunk_reader->page_end_row();
} else {
- right_row = _chunk_reader->page_end_row();
+ RETURN_IF_ERROR(_chunk_reader->ensure_first_data_page_parsed());
}
+ right_row = _chunk_reader->page_end_row();
RowRanges read_ranges;
_generate_read_ranges(RowRange {_current_row_index, right_row},
&read_ranges);
if (read_ranges.count() == 0) {
@@ -1521,6 +1521,14 @@ ScalarColumnReader<IN_COLLECTION,
OFFSET_INDEX>::materialize_dictionary_values(
template <bool IN_COLLECTION, bool OFFSET_INDEX>
Result<MutableColumnPtr> ScalarColumnReader<IN_COLLECTION,
OFFSET_INDEX>::dictionary_values(
const DataTypePtr& target_type) {
+ bool has_dict = false;
+ auto status = _chunk_reader->load_dictionary_page(&has_dict);
+ if (!status.ok()) {
+ return ResultError(std::move(status));
+ }
+ if (!has_dict) {
+ return ResultError(Status::NotSupported("Parquet column has no
reusable dictionary"));
+ }
Decoder* dictionary_decoder = _chunk_reader->dictionary_decoder();
if (dictionary_decoder == nullptr || dictionary_decoder->dictionary_size()
== 0) {
return ResultError(Status::NotSupported("Parquet column has no
reusable dictionary"));
@@ -1536,15 +1544,6 @@ Result<MutableColumnPtr>
ScalarColumnReader<IN_COLLECTION, OFFSET_INDEX>::dictio
return materialize_dictionary_values(ids.get(), target_type);
}
-template <bool IN_COLLECTION, bool OFFSET_INDEX>
-Status ScalarColumnReader<IN_COLLECTION,
OFFSET_INDEX>::_try_load_dict_page(bool* loaded,
-
bool* has_dict) {
- // _chunk_reader init will load first page header to check whether has
dict page
- *loaded = true;
- *has_dict = _chunk_reader->has_dict();
- return Status::OK();
-}
-
template <bool IN_COLLECTION, bool OFFSET_INDEX>
Status ScalarColumnReader<IN_COLLECTION, OFFSET_INDEX>::read_column_data(
ColumnPtr& doris_column, const DataTypePtr& type,
@@ -1587,10 +1586,10 @@ Status ScalarColumnReader<IN_COLLECTION,
OFFSET_INDEX>::read_column_data(
int64_t right_row = 0;
if constexpr (OFFSET_INDEX == false) {
RETURN_IF_ERROR(_chunk_reader->parse_page_header());
- right_row = _chunk_reader->page_end_row();
} else {
- right_row = _chunk_reader->page_end_row();
+ RETURN_IF_ERROR(_chunk_reader->ensure_first_data_page_parsed());
}
+ right_row = _chunk_reader->page_end_row();
do {
// generate the row ranges that should be read
diff --git
a/be/src/format_v2/table/iceberg_position_delete_sys_table_reader.cpp
b/be/src/format_v2/table/iceberg_position_delete_sys_table_reader.cpp
index 76d39c72fb1..b2b37c1bf09 100644
--- a/be/src/format_v2/table/iceberg_position_delete_sys_table_reader.cpp
+++ b/be/src/format_v2/table/iceberg_position_delete_sys_table_reader.cpp
@@ -316,12 +316,10 @@ Status
IcebergPositionDeleteSysTableV2Reader::_init_position_delete_reader() {
static constexpr const char* kPositionReaderProfile =
"IcebergPositionDeleteFileReader";
if (_position_reader_profile == nullptr) {
- _position_reader_profile =
_scanner_profile->get_child(kPositionReaderProfile);
- if (_position_reader_profile == nullptr) {
- // The outer system-table reader calls the inner reader
synchronously. Giving both the
- // same profile would nest identical counter pointers and
double-count every timer.
- _position_reader_profile =
_scanner_profile->create_child(kPositionReaderProfile);
- }
+ // The nested reader needs a distinct profile to avoid double-counting
its timers. Split
+ // readers share the scanner profile and initialize concurrently, so
lookup and creation
+ // must also be atomic to preserve the unique child-name invariant.
+ _position_reader_profile =
_scanner_profile->get_or_create_child(kPositionReaderProfile);
}
_position_reader = std::make_unique<PositionDeleteFileTableReader>();
RETURN_IF_ERROR(_position_reader->init({
diff --git a/be/src/runtime/runtime_profile.cpp
b/be/src/runtime/runtime_profile.cpp
index f48f2935369..36bc79b8215 100644
--- a/be/src/runtime/runtime_profile.cpp
+++ b/be/src/runtime/runtime_profile.cpp
@@ -393,6 +393,23 @@ RuntimeProfile* RuntimeProfile::create_child(const
std::string& name, bool inden
return child;
}
+RuntimeProfile* RuntimeProfile::get_or_create_child(const std::string& name,
bool indent,
+ bool prepend) {
+ std::lock_guard<std::mutex> l(_children_lock);
+ auto it = _child_map.find(name);
+ if (it != _child_map.end()) {
+ return it->second;
+ }
+
+ RuntimeProfile* child = _pool->add(new RuntimeProfile(name));
+ if (this->is_set_metadata()) {
+ child->set_metadata(this->metadata());
+ }
+ auto* location = !_children.empty() && prepend ? _children.front().first :
nullptr;
+ add_child_unlock(child, indent, location);
+ return child;
+}
+
void RuntimeProfile::add_child_unlock(RuntimeProfile* child, bool indent,
RuntimeProfile* loc) {
DCHECK(child != nullptr);
_child_map[child->_name] = child;
diff --git a/be/src/runtime/runtime_profile.h b/be/src/runtime/runtime_profile.h
index c5fe4aeb78d..54f2e80c89e 100644
--- a/be/src/runtime/runtime_profile.h
+++ b/be/src/runtime/runtime_profile.h
@@ -577,6 +577,11 @@ public:
/// otherwise appended after other child profiles.
RuntimeProfile* create_child(const std::string& name, bool indent = true,
bool prepend = false);
+ /// Returns an existing child profile with 'name', or creates it if
absent. Lookup and creation
+ /// are atomic so concurrent callers cannot race while initializing a
shared profile subtree.
+ RuntimeProfile* get_or_create_child(const std::string& name, bool indent =
true,
+ bool prepend = false);
+
// Merges the src profile into this one, combining counters that have an
identical
// path. Info strings from profiles are not merged. 'src' would be a const
if it
// weren't for locking.
diff --git a/be/test/format_v2/parquet/native_decoder_test.cpp
b/be/test/format_v2/parquet/native_decoder_test.cpp
index 3dfad7f4abd..b71e94222a3 100644
--- a/be/test/format_v2/parquet/native_decoder_test.cpp
+++ b/be/test/format_v2/parquet/native_decoder_test.cpp
@@ -387,6 +387,7 @@ public:
Status read_bytes(const uint8_t** buf, uint64_t offset, size_t
bytes_to_read,
const io::IOContext*) override {
+ ++_read_count;
if (offset > _data.size() || bytes_to_read > _data.size() - offset) {
return Status::IOError("out of bounds");
}
@@ -394,6 +395,7 @@ public:
return Status::OK();
}
Status read_bytes(Slice& slice, uint64_t offset, const io::IOContext*)
override {
+ ++_read_count;
if (offset > _data.size() || slice.size > _data.size() - offset) {
return Status::IOError("out of bounds");
}
@@ -402,9 +404,11 @@ public:
}
std::string path() override { return "memory.parquet"; }
int64_t mtime() const override { return 0; }
+ size_t read_count() const { return _read_count; }
private:
std::vector<uint8_t> _data;
+ size_t _read_count = 0;
};
class NativeDecoderMemoryFileReader final : public io::FileReader {
@@ -420,10 +424,12 @@ public:
size_t size() const override { return _data.size(); }
bool closed() const override { return _closed; }
int64_t mtime() const override { return 1; }
+ size_t read_count() const { return _read_count; }
protected:
Status read_at_impl(size_t offset, Slice result, size_t* bytes_read,
const io::IOContext*) override {
+ ++_read_count;
if (offset > _data.size() || result.size > _data.size() - offset) {
return Status::IOError("native decoder memory read exceeds file");
}
@@ -436,6 +442,7 @@ private:
std::vector<uint8_t> _data;
io::Path _path;
bool _closed = false;
+ size_t _read_count = 0;
};
std::shared_ptr<::parquet::ColumnDescriptor> descriptor(::parquet::Type::type
physical_type) {
@@ -487,6 +494,53 @@ std::vector<uint8_t> serialize_page(tparquet::PageHeader
header,
return bytes;
}
+std::vector<uint8_t> serialize_plain_int32_page(const std::vector<int32_t>&
values) {
+ tparquet::PageHeader header;
+ header.type = tparquet::PageType::DATA_PAGE;
+ header.__set_compressed_page_size(values.size() * sizeof(int32_t));
+ header.__set_uncompressed_page_size(values.size() * sizeof(int32_t));
+ header.__isset.data_page_header = true;
+ header.data_page_header.__set_num_values(values.size());
+ header.data_page_header.__set_encoding(tparquet::Encoding::PLAIN);
+
header.data_page_header.__set_definition_level_encoding(tparquet::Encoding::RLE);
+
header.data_page_header.__set_repetition_level_encoding(tparquet::Encoding::RLE);
+ const auto* value_bytes = reinterpret_cast<const uint8_t*>(values.data());
+ return serialize_page(
+ header, std::vector<uint8_t>(value_bytes, value_bytes +
header.compressed_page_size));
+}
+
+TEST(ParquetV2NativeDecoderTest, ColumnChunkInitDoesNotReadFirstDataPage) {
+ tparquet::PageHeader header;
+ header.type = tparquet::PageType::DATA_PAGE;
+ header.__set_compressed_page_size(sizeof(int32_t));
+ header.__set_uncompressed_page_size(sizeof(int32_t));
+ header.__isset.data_page_header = true;
+ header.data_page_header.__set_num_values(1);
+ header.data_page_header.__set_encoding(tparquet::Encoding::PLAIN);
+
header.data_page_header.__set_definition_level_encoding(tparquet::Encoding::RLE);
+
header.data_page_header.__set_repetition_level_encoding(tparquet::Encoding::RLE);
+ auto bytes = serialize_page(header, std::vector<uint8_t>(sizeof(int32_t)));
+ MemoryBufferedReader stream(bytes);
+
+ tparquet::ColumnChunk chunk;
+ chunk.meta_data.__set_type(tparquet::Type::INT32);
+ chunk.meta_data.__set_codec(tparquet::CompressionCodec::UNCOMPRESSED);
+ chunk.meta_data.__set_num_values(1);
+ chunk.meta_data.__set_total_compressed_size(bytes.size());
+ chunk.meta_data.__set_data_page_offset(0);
+ NativeFieldSchema field;
+ field.physical_type = tparquet::Type::INT32;
+ field.parquet_schema.__set_type(tparquet::Type::INT32);
+
field.parquet_schema.__set_repetition_type(tparquet::FieldRepetitionType::REQUIRED);
+ ParquetPageReadContext context(false, "");
+ ColumnChunkReader<false, false> reader(&stream, &chunk, &field, nullptr,
1, nullptr, context);
+
+ ASSERT_TRUE(reader.init().ok());
+ EXPECT_EQ(stream.read_count(), 0);
+ ASSERT_TRUE(reader.parse_page_header().ok());
+ EXPECT_GT(stream.read_count(), 0);
+}
+
Status materialize_level_only_page(bool data_page_v2, tparquet::Type::type
physical_type,
tparquet::Encoding::type encoding, bool
all_null) {
constexpr size_t VALUE_COUNT = 3;
@@ -541,6 +595,7 @@ Status materialize_level_only_page(bool data_page_v2,
tparquet::Type::type physi
ColumnChunkReader<false, false> chunk_reader(&reader, &chunk, &field,
nullptr, VALUE_COUNT,
nullptr, page_context);
RETURN_IF_ERROR(chunk_reader.init());
+ RETURN_IF_ERROR(chunk_reader.parse_page_header());
const auto load_status = chunk_reader.load_page_data();
EXPECT_TRUE(load_status.ok()) << load_status;
RETURN_IF_ERROR(load_status);
@@ -617,6 +672,7 @@ Status load_scripted_page(tparquet::PageHeader header,
const std::vector<uint8_t
ColumnChunkReader<false, false> chunk_reader(&reader, &chunk, &field,
nullptr, 1, nullptr,
context);
RETURN_IF_ERROR(chunk_reader.init());
+ RETURN_IF_ERROR(chunk_reader.parse_page_header());
return chunk_reader.load_page_data();
}
@@ -639,6 +695,7 @@ Status load_malformed_nested_page(tparquet::PageHeader
header, const std::vector
ColumnChunkReader<true, false> chunk_reader(&reader, &chunk, &field,
nullptr, 1, nullptr,
context);
RETURN_IF_ERROR(chunk_reader.init());
+ RETURN_IF_ERROR(chunk_reader.parse_page_header());
RETURN_IF_ERROR(chunk_reader.load_page_data());
std::vector<level_t> rep_levels;
size_t result_rows = 0;
@@ -674,6 +731,7 @@ Status materialize_plain_int96(const
std::vector<ParquetInt96Timestamp>& values,
ColumnChunkReader<false, false> chunk_reader(&reader, &chunk, &field,
nullptr, values.size(),
nullptr, page_context);
RETURN_IF_ERROR(chunk_reader.init());
+ RETURN_IF_ERROR(chunk_reader.parse_page_header());
RETURN_IF_ERROR(chunk_reader.load_page_data());
DataTypeDateTimeV2 type(6);
@@ -730,6 +788,7 @@ Status materialize_selected_plain_fixed(
ColumnChunkReader<false, false> chunk_reader(&reader, &chunk, &field,
nullptr, logical_values,
nullptr, page_context);
RETURN_IF_ERROR(chunk_reader.init());
+ RETURN_IF_ERROR(chunk_reader.parse_page_header());
RETURN_IF_ERROR(chunk_reader.load_page_data());
ParquetDecodeContext decode_context;
@@ -844,6 +903,7 @@ Status materialize_selected_dictionary_fixed(
ColumnChunkReader<false, false> chunk_reader(&reader, &chunk, &field,
nullptr, logical_values,
nullptr, page_context);
RETURN_IF_ERROR(chunk_reader.init());
+ RETURN_IF_ERROR(chunk_reader.parse_page_header());
RETURN_IF_ERROR(chunk_reader.load_page_data());
ParquetDecodeContext decode_context;
@@ -946,6 +1006,7 @@ Status materialize_selected_dictionary_strings(const
std::vector<std::string>& d
ColumnChunkReader<false, false> chunk_reader(&reader, &chunk, &field,
nullptr, logical_values,
nullptr, page_context);
RETURN_IF_ERROR(chunk_reader.init());
+ RETURN_IF_ERROR(chunk_reader.parse_page_header());
RETURN_IF_ERROR(chunk_reader.load_page_data());
DataTypeString type;
@@ -993,6 +1054,7 @@ TEST(ParquetV2NativeDecoderTest,
RawExprMapsNullableSparseRowsDirectly) {
ColumnChunkReader<false, false> chunk_reader(&reader, &chunk, &field,
nullptr, LOGICAL_VALUES,
nullptr, page_context);
ASSERT_TRUE(chunk_reader.init().ok());
+ ASSERT_TRUE(chunk_reader.parse_page_header().ok());
ASSERT_TRUE(chunk_reader.load_page_data().ok());
const std::vector<uint16_t> null_runs {1, 1, 2, 1, 2};
@@ -1341,8 +1403,10 @@ TEST(ParquetV2NativeDecoderTest,
DictionaryProbeMaterializesTypedValuesOnlyOnce)
ScalarColumnReader<false, false> reader(row_ranges, 2, chunk, nullptr,
nullptr, nullptr);
ASSERT_TRUE(reader.init(file, &field, bytes.size(), nullptr, "",
ParquetReaderCompat {}, true)
.ok());
+ EXPECT_EQ(file->read_count(), 0);
auto dictionary_result = reader.dictionary_values(field.data_type);
ASSERT_TRUE(dictionary_result.has_value()) << dictionary_result.error();
+ EXPECT_GT(file->read_count(), 0);
EXPECT_EQ(reader.dictionary_materialization_count_for_test(), 1);
FilterMap filter;
@@ -1371,6 +1435,83 @@ TEST(ParquetV2NativeDecoderTest,
DictionaryProbeMaterializesTypedValuesOnlyOnce)
EXPECT_EQ(reader.dictionary_materialization_count_for_test(), 1);
}
+TEST(ParquetV2NativeDecoderTest,
IndexedSkipLoadsDictionaryBeforeJumpingToDataPage) {
+ const std::array<int32_t, 2> dictionary {10, 20};
+ std::vector<uint8_t> dictionary_payload(sizeof(dictionary));
+ memcpy(dictionary_payload.data(), dictionary.data(),
dictionary_payload.size());
+ tparquet::PageHeader dictionary_header;
+ dictionary_header.type = tparquet::PageType::DICTIONARY_PAGE;
+ dictionary_header.__set_compressed_page_size(dictionary_payload.size());
+ dictionary_header.__set_uncompressed_page_size(dictionary_payload.size());
+ dictionary_header.__isset.dictionary_page_header = true;
+
dictionary_header.dictionary_page_header.__set_num_values(dictionary.size());
+
dictionary_header.dictionary_page_header.__set_encoding(tparquet::Encoding::PLAIN);
+
+ std::vector<uint8_t> bytes(1, 0);
+ const auto dictionary_page = serialize_page(dictionary_header,
dictionary_payload);
+ bytes.insert(bytes.end(), dictionary_page.begin(), dictionary_page.end());
+
+ auto make_data_page = [] {
+ tparquet::PageHeader header;
+ header.type = tparquet::PageType::DATA_PAGE;
+ header.__set_compressed_page_size(1);
+ header.__set_uncompressed_page_size(1);
+ header.__isset.data_page_header = true;
+ header.data_page_header.__set_num_values(1);
+
header.data_page_header.__set_encoding(tparquet::Encoding::RLE_DICTIONARY);
+
header.data_page_header.__set_definition_level_encoding(tparquet::Encoding::RLE);
+
header.data_page_header.__set_repetition_level_encoding(tparquet::Encoding::RLE);
+ return serialize_page(header, {0});
+ };
+ const size_t first_data_offset = bytes.size();
+ const auto first_data_page = make_data_page();
+ bytes.insert(bytes.end(), first_data_page.begin(), first_data_page.end());
+ const size_t second_data_offset = bytes.size();
+ const auto second_data_page = make_data_page();
+ bytes.insert(bytes.end(), second_data_page.begin(),
second_data_page.end());
+
+ tparquet::OffsetIndex offset_index;
+ tparquet::PageLocation first_location;
+ first_location.__set_offset(first_data_offset);
+ first_location.__set_compressed_page_size(first_data_page.size());
+ first_location.__set_first_row_index(0);
+ tparquet::PageLocation second_location;
+ second_location.__set_offset(second_data_offset);
+ second_location.__set_compressed_page_size(second_data_page.size());
+ second_location.__set_first_row_index(1);
+ offset_index.__set_page_locations({first_location, second_location});
+
+ tparquet::ColumnChunk chunk;
+ chunk.meta_data.__set_type(tparquet::Type::INT32);
+ chunk.meta_data.__set_codec(tparquet::CompressionCodec::UNCOMPRESSED);
+ chunk.meta_data.__set_num_values(2);
+ chunk.meta_data.__set_total_compressed_size(bytes.size() - 1);
+ chunk.meta_data.__set_dictionary_page_offset(1);
+ chunk.meta_data.__set_data_page_offset(first_data_offset);
+ NativeFieldSchema field;
+ field.physical_type = tparquet::Type::INT32;
+ field.parquet_schema.__set_type(tparquet::Type::INT32);
+
field.parquet_schema.__set_repetition_type(tparquet::FieldRepetitionType::REQUIRED);
+ MemoryBufferedReader stream(bytes);
+ ParquetPageReadContext context(false, "");
+ ColumnChunkReader<false, true> reader(&stream, &chunk, &field,
&offset_index, 2, nullptr,
+ context);
+
+ ASSERT_TRUE(reader.init().ok());
+ EXPECT_EQ(stream.read_count(), 0);
+ ASSERT_TRUE(reader.next_page().ok());
+ EXPECT_GT(stream.read_count(), 0);
+ bool has_dict = false;
+ const size_t reads_after_seek = stream.read_count();
+ ASSERT_TRUE(reader.load_dictionary_page(&has_dict).ok());
+ EXPECT_TRUE(has_dict);
+ EXPECT_EQ(stream.read_count(), reads_after_seek);
+ ASSERT_NE(reader.dictionary_decoder(), nullptr);
+ EXPECT_EQ(reader.dictionary_decoder()->dictionary_size(),
dictionary.size());
+ ASSERT_TRUE(reader.parse_page_header().ok());
+ EXPECT_EQ(reader.page_start_row(), 1);
+}
+
TEST(ParquetV2NativeDecoderTest,
DictionaryRepeatedRunsGatherDirectlyIntoDestination) {
for (const auto encoding :
{tparquet::Encoding::RLE_DICTIONARY,
tparquet::Encoding::PLAIN_DICTIONARY}) {
@@ -2860,6 +3001,7 @@ TEST(ParquetV2NativeDecoderTest,
DecompressionScratchStaysActiveUntilPageExhaust
static_cast<size_t>(LARGE_VALUE_COUNT) + 1, nullptr,
context);
ASSERT_TRUE(reader.init().ok());
+ ASSERT_TRUE(reader.parse_page_header().ok());
ASSERT_TRUE(reader.load_page_data().ok());
ASSERT_GT(reader.active_decoder_scratch_bytes(), 1UL << 20);
ASSERT_TRUE(reader.skip_values(LARGE_VALUE_COUNT).ok());
@@ -3083,6 +3225,218 @@ TEST(ParquetV2NativeDecoderTest,
ShiftedOffsetIndexFallsBackToSequentialPages) {
verify_fallback(true);
}
+TEST(ParquetV2NativeDecoderTest,
LazyFlatIndexedSkipValidatesFirstPageCardinality) {
+ const auto first_page = serialize_plain_int32_page({10});
+ const auto second_page = serialize_plain_int32_page({20, 21});
+ const auto third_page = serialize_plain_int32_page({30});
+ const auto fourth_page = serialize_plain_int32_page({40, 41});
+ std::vector<uint8_t> bytes = first_page;
+ const size_t second_offset = bytes.size();
+ bytes.insert(bytes.end(), second_page.begin(), second_page.end());
+ const size_t third_offset = bytes.size();
+ bytes.insert(bytes.end(), third_page.begin(), third_page.end());
+ const size_t fourth_offset = bytes.size();
+ bytes.insert(bytes.end(), fourth_page.begin(), fourth_page.end());
+
+ tparquet::OffsetIndex offset_index;
+ std::vector<tparquet::PageLocation> locations(4);
+ locations[0].__set_offset(0);
+ locations[0].__set_compressed_page_size(first_page.size());
+ locations[0].__set_first_row_index(0);
+ locations[1].__set_offset(second_offset);
+ locations[1].__set_compressed_page_size(second_page.size());
+ locations[1].__set_first_row_index(2);
+ locations[2].__set_offset(third_offset);
+ locations[2].__set_compressed_page_size(third_page.size());
+ locations[2].__set_first_row_index(4);
+ locations[3].__set_offset(fourth_offset);
+ locations[3].__set_compressed_page_size(fourth_page.size());
+ locations[3].__set_first_row_index(5);
+ offset_index.__set_page_locations(locations);
+
+ tparquet::ColumnChunk chunk;
+ chunk.meta_data.__set_type(tparquet::Type::INT32);
+ chunk.meta_data.__set_codec(tparquet::CompressionCodec::UNCOMPRESSED);
+ chunk.meta_data.__set_num_values(6);
+ chunk.meta_data.__set_total_compressed_size(bytes.size());
+ chunk.meta_data.__set_data_page_offset(0);
+ NativeFieldSchema field;
+ field.physical_type = tparquet::Type::INT32;
+ field.data_type = std::make_shared<DataTypeInt32>();
+ field.parquet_schema.__set_type(tparquet::Type::INT32);
+
field.parquet_schema.__set_repetition_type(tparquet::FieldRepetitionType::REQUIRED);
+ auto file = std::make_shared<NativeDecoderMemoryFileReader>(bytes);
+ const auto row_ranges = ::doris::RowRanges::create_single(2, 4);
+ ScalarColumnReader<false, true> reader(row_ranges, 6, chunk,
&offset_index, nullptr, nullptr);
+ ASSERT_TRUE(reader.init(file, &field, bytes.size(), nullptr, "",
ParquetReaderCompat {}, true)
+ .ok());
+
+ FilterMap filter;
+ ASSERT_TRUE(filter.init(nullptr, 2, false).ok());
+ ColumnPtr values = ColumnInt32::create();
+ size_t rows = 0;
+ bool eof = false;
+ const auto status = reader.read_column_data(values, field.data_type,
nullptr, filter, 2, &rows,
+ &eof, false);
+ EXPECT_TRUE(status.is<ErrorCode::CORRUPTION>()) << status;
+}
+
+TEST(ParquetV2NativeDecoderTest,
LazyFixedWidthFilterUsesReconciledFirstPageRange) {
+ const auto first_page = serialize_plain_int32_page({10, 11});
+ const auto second_page = serialize_plain_int32_page({20});
+ std::vector<uint8_t> bytes = first_page;
+ bytes.insert(bytes.end(), second_page.begin(), second_page.end());
+ bytes.push_back(0);
+
+ tparquet::OffsetIndex offset_index;
+ tparquet::PageLocation first_location;
+ first_location.__set_offset(0);
+ first_location.__set_compressed_page_size(first_page.size() + 1);
+ first_location.__set_first_row_index(0);
+ tparquet::PageLocation second_location;
+ second_location.__set_offset(first_page.size() + 1);
+ second_location.__set_compressed_page_size(second_page.size());
+ second_location.__set_first_row_index(1);
+ offset_index.__set_page_locations({first_location, second_location});
+
+ tparquet::ColumnChunk chunk;
+ chunk.meta_data.__set_type(tparquet::Type::INT32);
+ chunk.meta_data.__set_codec(tparquet::CompressionCodec::UNCOMPRESSED);
+ chunk.meta_data.__set_num_values(3);
+ chunk.meta_data.__set_total_compressed_size(bytes.size());
+ chunk.meta_data.__set_data_page_offset(0);
+ chunk.meta_data.__set_encodings({tparquet::Encoding::PLAIN});
+ NativeFieldSchema field;
+ field.physical_type = tparquet::Type::INT32;
+ field.data_type = std::make_shared<DataTypeInt32>();
+ field.parquet_schema.__set_type(tparquet::Type::INT32);
+
field.parquet_schema.__set_repetition_type(tparquet::FieldRepetitionType::REQUIRED);
+ auto file = std::make_shared<NativeDecoderMemoryFileReader>(bytes);
+ const auto row_ranges = ::doris::RowRanges::create_single(0, 2);
+ ScalarColumnReader<false, true> reader(row_ranges, 3, chunk,
&offset_index, nullptr, nullptr);
+ ASSERT_TRUE(reader.init(file, &field, bytes.size(), nullptr, "",
ParquetReaderCompat {}, true)
+ .ok());
+
+ FilterMap filter;
+ ASSERT_TRUE(filter.init(nullptr, 2, false).ok());
+ IColumn::Filter row_filter;
+ size_t rows = 0;
+ bool eof = false;
+ bool used_filter = false;
+ const auto status = reader.read_fixed_width_filter(
+ {create_int32_raw_comparison(0, "ge", TExprOpcode::GE, 0)}, 0,
filter, 2, nullptr,
+ &row_filter, &rows, &eof, &used_filter);
+ ASSERT_TRUE(status.ok()) << status;
+ EXPECT_TRUE(used_filter);
+ EXPECT_EQ(rows, 2);
+ EXPECT_EQ(row_filter, (IColumn::Filter {1, 1}));
+}
+
+TEST(ParquetV2NativeDecoderTest,
LazyFlatIndexedFallbackUsesReconciledFirstPageRange) {
+ const auto first_page = serialize_plain_int32_page({10, 11});
+ const auto second_page = serialize_plain_int32_page({20});
+ std::vector<uint8_t> bytes = first_page;
+ bytes.insert(bytes.end(), second_page.begin(), second_page.end());
+ bytes.push_back(0);
+
+ tparquet::OffsetIndex offset_index;
+ tparquet::PageLocation first_location;
+ first_location.__set_offset(0);
+ first_location.__set_compressed_page_size(first_page.size() + 1);
+ first_location.__set_first_row_index(0);
+ tparquet::PageLocation second_location;
+ second_location.__set_offset(first_page.size() + 1);
+ second_location.__set_compressed_page_size(second_page.size());
+ second_location.__set_first_row_index(1);
+ offset_index.__set_page_locations({first_location, second_location});
+
+ tparquet::ColumnChunk chunk;
+ chunk.meta_data.__set_type(tparquet::Type::INT32);
+ chunk.meta_data.__set_codec(tparquet::CompressionCodec::UNCOMPRESSED);
+ chunk.meta_data.__set_num_values(3);
+ chunk.meta_data.__set_total_compressed_size(bytes.size());
+ chunk.meta_data.__set_data_page_offset(0);
+ NativeFieldSchema field;
+ field.physical_type = tparquet::Type::INT32;
+ field.data_type = std::make_shared<DataTypeInt32>();
+ field.parquet_schema.__set_type(tparquet::Type::INT32);
+
field.parquet_schema.__set_repetition_type(tparquet::FieldRepetitionType::REQUIRED);
+ auto file = std::make_shared<NativeDecoderMemoryFileReader>(bytes);
+ const auto row_ranges = ::doris::RowRanges::create_single(0, 2);
+ ScalarColumnReader<false, true> reader(row_ranges, 3, chunk,
&offset_index, nullptr, nullptr);
+ ASSERT_TRUE(reader.init(file, &field, bytes.size(), nullptr, "",
ParquetReaderCompat {}, true)
+ .ok());
+
+ FilterMap filter;
+ ASSERT_TRUE(filter.init(nullptr, 2, false).ok());
+ ColumnPtr values = ColumnInt32::create();
+ size_t rows = 0;
+ bool eof = false;
+ ASSERT_TRUE(
+ reader.read_column_data(values, field.data_type, nullptr, filter,
2, &rows, &eof, false)
+ .ok());
+ ASSERT_EQ(rows, 2);
+ EXPECT_EQ(assert_cast<const ColumnInt32&>(*values).get_data(),
+ (ColumnInt32::Container {10, 11}));
+}
+
+TEST(ParquetV2NativeDecoderTest, LazyNestedV2SeekValidatesFirstPageRowRange) {
+ auto make_page = [](int32_t value) {
+ const std::vector<uint8_t> repetition_levels {2, 0};
+ std::vector<uint8_t> payload = repetition_levels;
+ const auto* value_bytes = reinterpret_cast<const uint8_t*>(&value);
+ payload.insert(payload.end(), value_bytes, value_bytes +
sizeof(value));
+ tparquet::PageHeader header;
+ header.type = tparquet::PageType::DATA_PAGE_V2;
+ header.__set_compressed_page_size(payload.size());
+ header.__set_uncompressed_page_size(payload.size());
+ header.__isset.data_page_header_v2 = true;
+ header.data_page_header_v2.__set_num_values(1);
+ header.data_page_header_v2.__set_num_rows(1);
+ header.data_page_header_v2.__set_num_nulls(0);
+ header.data_page_header_v2.__set_encoding(tparquet::Encoding::PLAIN);
+
header.data_page_header_v2.__set_repetition_levels_byte_length(repetition_levels.size());
+ header.data_page_header_v2.__set_definition_levels_byte_length(0);
+ header.data_page_header_v2.__set_is_compressed(false);
+ return serialize_page(header, payload);
+ };
+ const auto first_page = make_page(10);
+ const auto second_page = make_page(20);
+ std::vector<uint8_t> bytes = first_page;
+ const size_t second_offset = bytes.size();
+ bytes.insert(bytes.end(), second_page.begin(), second_page.end());
+
+ tparquet::OffsetIndex offset_index;
+ tparquet::PageLocation first_location;
+ first_location.__set_offset(0);
+ first_location.__set_compressed_page_size(first_page.size());
+ first_location.__set_first_row_index(0);
+ tparquet::PageLocation second_location;
+ second_location.__set_offset(second_offset);
+ second_location.__set_compressed_page_size(second_page.size());
+ second_location.__set_first_row_index(2);
+ offset_index.__set_page_locations({first_location, second_location});
+
+ tparquet::ColumnChunk chunk;
+ chunk.meta_data.__set_type(tparquet::Type::INT32);
+ chunk.meta_data.__set_codec(tparquet::CompressionCodec::UNCOMPRESSED);
+ chunk.meta_data.__set_num_values(2);
+ chunk.meta_data.__set_total_compressed_size(bytes.size());
+ chunk.meta_data.__set_data_page_offset(0);
+ NativeFieldSchema field;
+ field.physical_type = tparquet::Type::INT32;
+ field.repetition_level = 1;
+ ParquetPageReadContext context(false, "");
+ MemoryBufferedReader stream(bytes);
+ ColumnChunkReader<true, true> reader(&stream, &chunk, &field,
&offset_index, 3, nullptr,
+ context);
+
+ ASSERT_TRUE(reader.init().ok());
+ EXPECT_EQ(stream.read_count(), 0);
+ const auto status = reader.seek_to_nested_row(2);
+ EXPECT_TRUE(status.is<ErrorCode::CORRUPTION>()) << status;
+}
+
TEST(ParquetV2NativeDecoderTest,
FlatPagesRejectLogicalAndPhysicalCardinalityMismatch) {
auto init_chunk = [](tparquet::PageHeader header, bool with_offset_index) {
std::vector<uint8_t>
payload(static_cast<size_t>(header.compressed_page_size), 0);
@@ -3108,11 +3462,13 @@ TEST(ParquetV2NativeDecoderTest,
FlatPagesRejectLogicalAndPhysicalCardinalityMis
if (with_offset_index) {
ColumnChunkReader<false, true> reader_with_index(&reader, &chunk,
&field, &offset_index,
1, nullptr,
context);
- return reader_with_index.init();
+ RETURN_IF_ERROR(reader_with_index.init());
+ return reader_with_index.parse_page_header();
}
ColumnChunkReader<false, false> sequential_reader(&reader, &chunk,
&field, nullptr, 1,
nullptr, context);
- return sequential_reader.init();
+ RETURN_IF_ERROR(sequential_reader.init());
+ return sequential_reader.parse_page_header();
};
tparquet::PageHeader v2;
@@ -3181,7 +3537,8 @@ TEST(ParquetV2NativeDecoderTest,
NestedV2PageRejectsOffsetIndexRowSpanMismatch)
ColumnChunkReader<true, true> reader(&stream, &chunk, &field,
&offset_index,
/*total_rows=*/2, nullptr, context);
- const auto status = reader.init();
+ ASSERT_TRUE(reader.init().ok());
+ const auto status = reader.parse_page_header();
EXPECT_TRUE(status.is<ErrorCode::CORRUPTION>()) << status;
}
@@ -3390,6 +3747,7 @@ TEST(ParquetV2NativeDecoderTest,
NestedV1ContinuationRemainsValidAfterFirstRowSt
ColumnChunkReader<true, false> chunk_reader(&reader, &chunk, &field,
nullptr, 1, nullptr,
context);
ASSERT_TRUE(chunk_reader.init().ok());
+ ASSERT_TRUE(chunk_reader.parse_page_header().ok());
ASSERT_TRUE(chunk_reader.load_page_data().ok());
std::vector<level_t> levels;
size_t rows = 0;
@@ -3445,6 +3803,7 @@ TEST(ParquetV2NativeDecoderTest,
NestedV1IgnoresUnverifiableOffsetIndexRows) {
ColumnChunkReader<true, true> chunk_reader(&stream, &chunk, &field,
&offset_index, 1, nullptr,
context);
ASSERT_TRUE(chunk_reader.init().ok());
+ ASSERT_TRUE(chunk_reader.parse_page_header().ok());
ASSERT_TRUE(chunk_reader.load_page_data().ok());
std::vector<level_t> levels;
size_t rows = 0;
@@ -3456,6 +3815,141 @@ TEST(ParquetV2NativeDecoderTest,
NestedV1IgnoresUnverifiableOffsetIndexRows) {
EXPECT_EQ(levels, std::vector<level_t>({0, 1, 1}));
}
+TEST(ParquetV2NativeDecoderTest, LazyNestedV1SeekDoesNotOutrunPhysicalPages) {
+ auto make_page = [] {
+ tparquet::PageHeader header;
+ header.type = tparquet::PageType::DATA_PAGE;
+ const std::vector<uint8_t> payload {2, 0, 0, 0, 2, 0, 0, 0, 0, 0};
+ header.__set_compressed_page_size(payload.size());
+ header.__set_uncompressed_page_size(payload.size());
+ header.__isset.data_page_header = true;
+ header.data_page_header.__set_num_values(1);
+ header.data_page_header.__set_encoding(tparquet::Encoding::PLAIN);
+
header.data_page_header.__set_repetition_level_encoding(tparquet::Encoding::RLE);
+
header.data_page_header.__set_definition_level_encoding(tparquet::Encoding::RLE);
+ return serialize_page(header, payload);
+ };
+ const auto first_page = make_page();
+ const auto second_page = make_page();
+ std::vector<uint8_t> bytes = first_page;
+ bytes.insert(bytes.end(), second_page.begin(), second_page.end());
+
+ MemoryBufferedReader stream(bytes);
+ tparquet::ColumnChunk chunk;
+ chunk.meta_data.__set_type(tparquet::Type::INT32);
+ chunk.meta_data.__set_codec(tparquet::CompressionCodec::UNCOMPRESSED);
+ chunk.meta_data.__set_num_values(2);
+ chunk.meta_data.__set_total_compressed_size(bytes.size());
+ chunk.meta_data.__set_data_page_offset(0);
+ NativeFieldSchema field;
+ field.physical_type = tparquet::Type::INT32;
+ field.repetition_level = 1;
+ tparquet::OffsetIndex offset_index;
+ tparquet::PageLocation first_location;
+ first_location.__set_offset(0);
+ first_location.__set_compressed_page_size(first_page.size());
+ first_location.__set_first_row_index(0);
+ tparquet::PageLocation second_location;
+ second_location.__set_offset(first_page.size());
+ second_location.__set_compressed_page_size(second_page.size());
+ second_location.__set_first_row_index(1);
+ offset_index.__set_page_locations({first_location, second_location});
+ ParquetPageReadContext context(false, "");
+ ColumnChunkReader<true, true> chunk_reader(&stream, &chunk, &field,
&offset_index, 2, nullptr,
+ context);
+
+ ASSERT_TRUE(chunk_reader.init().ok());
+ EXPECT_EQ(stream.read_count(), 0);
+ ASSERT_TRUE(chunk_reader.seek_to_nested_row(1).ok());
+ std::vector<level_t> levels;
+ size_t rows = 0;
+ bool cross_page = false;
+ ASSERT_TRUE(chunk_reader.load_page_nested_rows(levels, 1, &rows,
&cross_page).ok());
+ if (cross_page) {
+ const auto status = chunk_reader.load_cross_page_nested_row(levels,
&cross_page);
+ ASSERT_TRUE(status.ok()) << status;
+ }
+ EXPECT_EQ(rows, 1);
+ EXPECT_FALSE(cross_page);
+}
+
+TEST(ParquetV2NativeDecoderTest,
LazyDictionaryNestedV1SeekChecksFirstDataPage) {
+ tparquet::PageHeader dictionary_header;
+ dictionary_header.type = tparquet::PageType::DICTIONARY_PAGE;
+ dictionary_header.__set_compressed_page_size(sizeof(int32_t));
+ dictionary_header.__set_uncompressed_page_size(sizeof(int32_t));
+ dictionary_header.__isset.dictionary_page_header = true;
+ dictionary_header.dictionary_page_header.__set_num_values(1);
+
dictionary_header.dictionary_page_header.__set_encoding(tparquet::Encoding::PLAIN);
+ const int32_t dictionary_value = 7;
+ const auto* dictionary_bytes = reinterpret_cast<const
uint8_t*>(&dictionary_value);
+ auto bytes = serialize_page(
+ dictionary_header,
+ std::vector<uint8_t>(dictionary_bytes, dictionary_bytes +
sizeof(dictionary_value)));
+
+ auto make_data_page = [] {
+ tparquet::PageHeader header;
+ header.type = tparquet::PageType::DATA_PAGE;
+ const std::vector<uint8_t> payload {2, 0, 0, 0, 2, 0, 0, 0, 0, 0};
+ header.__set_compressed_page_size(payload.size());
+ header.__set_uncompressed_page_size(payload.size());
+ header.__isset.data_page_header = true;
+ header.data_page_header.__set_num_values(1);
+ header.data_page_header.__set_encoding(tparquet::Encoding::PLAIN);
+
header.data_page_header.__set_repetition_level_encoding(tparquet::Encoding::RLE);
+
header.data_page_header.__set_definition_level_encoding(tparquet::Encoding::RLE);
+ return serialize_page(header, payload);
+ };
+ const auto first_page = make_data_page();
+ const size_t first_page_offset = bytes.size();
+ bytes.insert(bytes.end(), first_page.begin(), first_page.end());
+ const auto second_page = make_data_page();
+ const size_t second_page_offset = bytes.size();
+ bytes.insert(bytes.end(), second_page.begin(), second_page.end());
+ const size_t chunk_size = bytes.size();
+ bytes.resize(chunk_size + 16, 0);
+
+ MemoryBufferedReader stream(bytes);
+ tparquet::ColumnChunk chunk;
+ chunk.meta_data.__set_type(tparquet::Type::INT32);
+ chunk.meta_data.__set_codec(tparquet::CompressionCodec::UNCOMPRESSED);
+ chunk.meta_data.__set_num_values(2);
+ chunk.meta_data.__set_total_compressed_size(chunk_size);
+ chunk.meta_data.__set_dictionary_page_offset(0);
+ chunk.meta_data.__set_data_page_offset(first_page_offset);
+ NativeFieldSchema field;
+ field.physical_type = tparquet::Type::INT32;
+ field.repetition_level = 1;
+ tparquet::OffsetIndex offset_index;
+ tparquet::PageLocation first_location;
+ first_location.__set_offset(first_page_offset);
+ first_location.__set_compressed_page_size(first_page.size());
+ first_location.__set_first_row_index(0);
+ tparquet::PageLocation second_location;
+ second_location.__set_offset(second_page_offset);
+ second_location.__set_compressed_page_size(second_page.size());
+ second_location.__set_first_row_index(1);
+ offset_index.__set_page_locations({first_location, second_location});
+ ColumnChunkRange padded_range {.offset = 0, .length = bytes.size()};
+ ParquetPageReadContext context(false, "");
+ ColumnChunkReader<true, true> chunk_reader(&stream, &chunk, &field,
&offset_index, 2, nullptr,
+ context, &padded_range);
+
+ ASSERT_TRUE(chunk_reader.init().ok());
+ EXPECT_EQ(stream.read_count(), 0);
+ ASSERT_TRUE(chunk_reader.seek_to_nested_row(1).ok());
+ std::vector<level_t> levels;
+ size_t rows = 0;
+ bool cross_page = false;
+ ASSERT_TRUE(chunk_reader.load_page_nested_rows(levels, 1, &rows,
&cross_page).ok());
+ if (cross_page) {
+ const auto status = chunk_reader.load_cross_page_nested_row(levels,
&cross_page);
+ ASSERT_TRUE(status.ok()) << status;
+ }
+ EXPECT_EQ(rows, 1);
+ EXPECT_FALSE(cross_page);
+}
+
TEST(ParquetV2NativeDecoderTest,
NestedV1DiscardedOffsetIndexStopsAtLogicalChunkEnd) {
tparquet::PageHeader header;
header.type = tparquet::PageType::DATA_PAGE;
@@ -3493,6 +3987,7 @@ TEST(ParquetV2NativeDecoderTest,
NestedV1DiscardedOffsetIndexStopsAtLogicalChunk
ColumnChunkReader<true, true> chunk_reader(&stream, &chunk, &field,
&offset_index, 1, nullptr,
context, &padded_range);
ASSERT_TRUE(chunk_reader.init().ok());
+ ASSERT_TRUE(chunk_reader.parse_page_header().ok());
ASSERT_TRUE(chunk_reader.load_page_data().ok());
std::vector<level_t> levels;
size_t rows = 0;
@@ -3650,6 +4145,7 @@ TEST(ParquetV2NativeDecoderTest,
OptionalV2FixedWidthPageRejectsExtentBeforeAllo
ColumnChunkReader<false, false> reader(&stream, &chunk, &field,
nullptr, 1, nullptr,
context);
ASSERT_TRUE(reader.init().ok());
+ ASSERT_TRUE(reader.parse_page_header().ok());
EXPECT_TRUE(reader.load_page_data().is<ErrorCode::CORRUPTION>());
EXPECT_LT(reader.retained_decoder_scratch_bytes(), 64UL << 10);
}
@@ -3695,6 +4191,7 @@ TEST(ParquetV2NativeDecoderTest,
RepeatedV2FixedWidthPageRejectsExtentBeforeAllo
ParquetPageReadContext context(false, "");
ColumnChunkReader<true, false> reader(&stream, &chunk, &field, nullptr, 1,
nullptr, context);
ASSERT_TRUE(reader.init().ok());
+ ASSERT_TRUE(reader.parse_page_header().ok());
EXPECT_TRUE(reader.load_page_data().is<ErrorCode::CORRUPTION>());
EXPECT_LT(reader.retained_decoder_scratch_bytes(), 64UL << 10);
}
@@ -3733,6 +4230,7 @@ TEST(ParquetV2NativeDecoderTest,
VariableWidthDataPagePreflightsCompressedExtent
ParquetPageReadContext context(false, "");
ColumnChunkReader<false, false> reader(&stream, &chunk, &field, nullptr,
1, nullptr, context);
ASSERT_TRUE(reader.init().ok());
+ ASSERT_TRUE(reader.parse_page_header().ok());
EXPECT_TRUE(reader.load_page_data().is<ErrorCode::CORRUPTION>());
EXPECT_LT(reader.retained_decoder_scratch_bytes(), 64UL << 10);
EXPECT_TRUE(load_scripted_page(header, payload,
tparquet::CompressionCodec::SNAPPY, true,
@@ -3933,6 +4431,7 @@ TEST(ParquetV2NativeDecoderTest,
ColumnChunkSkipsIndexPageBeforeInitializingData
context);
const auto init_status = chunk_reader.init();
ASSERT_TRUE(init_status.ok()) << init_status;
+ ASSERT_TRUE(chunk_reader.parse_page_header().ok());
EXPECT_EQ(chunk_reader.remaining_num_values(), 1);
ASSERT_TRUE(chunk_reader.load_page_data().ok());
}
@@ -3974,6 +4473,7 @@ TEST(ParquetV2NativeDecoderTest,
ColumnChunkSkipsUnknownAuxiliaryPage) {
context);
const auto init_status = chunk_reader.init();
ASSERT_TRUE(init_status.ok()) << init_status;
+ ASSERT_TRUE(chunk_reader.parse_page_header().ok());
EXPECT_EQ(chunk_reader.remaining_num_values(), 1);
}
diff --git a/be/test/runtime/runtime_profile_test.cpp
b/be/test/runtime/runtime_profile_test.cpp
index 2734084e9cb..e39658d0b48 100644
--- a/be/test/runtime/runtime_profile_test.cpp
+++ b/be/test/runtime/runtime_profile_test.cpp
@@ -19,8 +19,11 @@
#include <gtest/gtest.h>
+#include <barrier>
#include <cstdlib>
#include <sstream>
+#include <thread>
+#include <vector>
#include "common/exception.h"
#include "common/object_pool.h"
@@ -578,4 +581,32 @@ TEST(RuntimeProfileTest, TestGetChild) {
ASSERT_EQ(child2, root.get_child("Child2"));
}
+TEST(RuntimeProfileTest, ConcurrentGetOrCreateChildReturnsSingleSharedProfile)
{
+ RuntimeProfile root("Root");
+ constexpr size_t kThreadCount = 32;
+ std::barrier start(kThreadCount);
+ std::vector<RuntimeProfile*> children(kThreadCount);
+ std::vector<std::thread> threads;
+ threads.reserve(kThreadCount);
+
+ for (size_t i = 0; i < kThreadCount; ++i) {
+ threads.emplace_back([&, i] {
+ start.arrive_and_wait();
+ children[i] = root.get_or_create_child("SharedChild");
+ });
+ }
+ for (auto& thread : threads) {
+ thread.join();
+ }
+
+ ASSERT_NE(children.front(), nullptr);
+ for (const auto* child : children) {
+ EXPECT_EQ(child, children.front());
+ }
+ std::vector<RuntimeProfile*> profile_children;
+ root.get_children(&profile_children);
+ ASSERT_EQ(profile_children.size(), 1);
+ EXPECT_EQ(profile_children.front(), children.front());
+}
+
} // namespace doris
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]