From 098a3e56ee2d79c41438e94d572bc00ead790f0c Mon Sep 17 00:00:00 2001 From: Nilanjan Chatterjee Date: Sun, 11 Oct 2026 14:39:27 -0500 Subject: [PATCH] GH-37875: [C++][Parquet] Preserve large_string/large_binary dictionary value type on read When the stored Arrow schema has dictionary or dictionary, use that value type instead of the inferred narrow one. TransferDictionary already casts the decoded dictionary to the requested type. Co-Authored-By: Claude Opus 5.5 --- .../parquet/arrow/arrow_reader_writer_test.cc | 16 ++++++++++++++++ cpp/src/parquet/arrow/schema.cc | 11 ++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/cpp/src/parquet/arrow/arrow_reader_writer_test.cc b/cpp/src/parquet/arrow/arrow_reader_writer_test.cc index d858962d4728..74ee0fc1c837 100644 --- a/cpp/src/parquet/arrow/arrow_reader_writer_test.cc +++ b/cpp/src/parquet/arrow/arrow_reader_writer_test.cc @@ -3811,6 +3811,22 @@ TEST(TestArrowReadWrite, DictionaryIndexBitwidthRoundtrip) { } } +TEST(TestArrowReadWrite, DictionaryValueTypeRoundtrip) { + // GH-37875: the binary layout of dictionary values should be preserved + for (const auto& value_type : {::arrow::utf8(), ::arrow::large_utf8(), + ::arrow::binary(), ::arrow::large_binary()}) { + ARROW_SCOPED_TRACE("value_type = ", *value_type); + auto dict_type = ::arrow::dictionary(::arrow::int32(), value_type); + auto schema = ::arrow::schema({field("dictionary", dict_type)}); + auto array = DictArrayFromJSON(dict_type, R"([0, 1, null, 0])", R"(["foo", "bar"])"); + auto table = Table::Make(schema, {array}); + auto arrow_writer_props = + parquet::ArrowWriterProperties::Builder().store_schema()->build(); + + CheckSimpleRoundtrip(table, table->num_rows(), arrow_writer_props); + } +} + TEST(TestArrowWrite, CheckChunkSize) { const int num_columns = 2; const int num_rows = 128; diff --git a/cpp/src/parquet/arrow/schema.cc b/cpp/src/parquet/arrow/schema.cc index bc4de6c39b5e..d25a3f73a245 100644 --- a/cpp/src/parquet/arrow/schema.cc +++ b/cpp/src/parquet/arrow/schema.cc @@ -1110,8 +1110,17 @@ Result ApplyOriginalStorageMetadata(const Field& origin_field, // so no need to recurse on value types. const auto& dict_origin_type = checked_cast(*origin_type); + auto value_type = inferred_type; + const auto& origin_value_type = dict_origin_type.value_type(); + // Restore large dictionary values (GH-37875) + if ((origin_value_type->id() == ::arrow::Type::LARGE_STRING && + inferred_type->id() == ::arrow::Type::STRING) || + (origin_value_type->id() == ::arrow::Type::LARGE_BINARY && + inferred_type->id() == ::arrow::Type::BINARY)) { + value_type = origin_value_type; + } inferred->field = inferred->field->WithType(::arrow::dictionary( - dict_origin_type.index_type(), inferred_type, dict_origin_type.ordered())); + dict_origin_type.index_type(), value_type, dict_origin_type.ordered())); modified = true; }