wgtmac commented on code in PR #50807:
URL: https://github.com/apache/arrow/pull/50807#discussion_r3869837838


##########
cpp/src/parquet/statistics.h:
##########
@@ -259,6 +274,29 @@ class PARQUET_EXPORT Statistics {
       std::optional<bool> is_max_value_exact,
       ::arrow::MemoryPool* pool = ::arrow::default_memory_pool());
 
+  /// \brief Create a new statistics instance given a column schema
+  /// definition and preexisting state
+  /// \param[in] descr the column schema
+  /// \param[in] encoded_min the encoded minimum value
+  /// \param[in] encoded_max the encoded maximum value
+  /// \param[in] num_values total number of values
+  /// \param[in] null_count number of null values
+  /// \param[in] distinct_count number of distinct values
+  /// \param[in] nan_count number of NaN values, if available
+  /// \param[in] has_min_max whether the min/max statistics are set
+  /// \param[in] has_null_count whether the null_count statistics are set
+  /// \param[in] has_distinct_count whether the distinct_count statistics are 
set
+  /// \param[in] is_min_value_exact whether the min value is exact
+  /// \param[in] is_max_value_exact whether the max value is exact
+  /// \param[in] pool a memory pool to use for any memory allocations, optional
+  static std::shared_ptr<Statistics> Make(

Review Comment:
   Why not just directly extending the above Make function? We already have 
several overloads now.



##########
cpp/src/parquet/encoding_test.cc:
##########
@@ -466,6 +469,70 @@ TEST(TestDictionaryEncoding, CannotDictDecodeBoolean) {
   ASSERT_THROW(MakeDictDecoder<BooleanType>(nullptr), ParquetException);
 }
 
+template <typename DType, typename UInt, size_t NumValues>
+void TestFloatingDictionaryBits(const std::array<UInt, NumValues>& bits,
+                                int num_entries) {
+  using T = typename DType::c_type;
+  std::array<T, NumValues> values;
+  std::transform(bits.begin(), bits.end(), values.begin(),
+                 [](UInt value) { return ::arrow::util::SafeCopy<T>(value); });
+
+  auto encoder = MakeTypedEncoder<DType>(Encoding::PLAIN, true);

Review Comment:
   
   ```suggestion
     auto encoder = MakeTypedEncoder<DType>(Encoding::PLAIN, 
/*use_dictionary=*/true);
   ```
   
   Same apply to others



##########
cpp/src/parquet/encoding_test.cc:
##########
@@ -466,6 +469,70 @@ TEST(TestDictionaryEncoding, CannotDictDecodeBoolean) {
   ASSERT_THROW(MakeDictDecoder<BooleanType>(nullptr), ParquetException);
 }
 
+template <typename DType, typename UInt, size_t NumValues>
+void TestFloatingDictionaryBits(const std::array<UInt, NumValues>& bits,
+                                int num_entries) {
+  using T = typename DType::c_type;
+  std::array<T, NumValues> values;
+  std::transform(bits.begin(), bits.end(), values.begin(),
+                 [](UInt value) { return ::arrow::util::SafeCopy<T>(value); });
+
+  auto encoder = MakeTypedEncoder<DType>(Encoding::PLAIN, true);
+  auto dictionary = dynamic_cast<DictEncoder<DType>*>(encoder.get());
+  ASSERT_NE(nullptr, dictionary);
+  encoder->Put(values.data(), values.size());
+  ASSERT_EQ(num_entries, dictionary->num_entries());
+
+  auto buffer = AllocateBuffer(default_memory_pool(), 
dictionary->dict_encoded_size());
+  dictionary->WriteDict(buffer->mutable_data());
+  const UInt* encoded = reinterpret_cast<const UInt*>(buffer->data());
+  for (int value_index = 0; value_index < num_entries; ++value_index) {
+    EXPECT_EQ(bits[value_index], encoded[value_index]);

Review Comment:
   This is a little bit fraigle since it enforces that all distinct values 
should appear in the beginning of `bits`. Should we remove `num_entries` and 
add a `const std::array<UInt, NumValues>& expected_bits` to the input parameter 
instead?



##########
cpp/src/parquet/statistics.h:
##########
@@ -279,6 +317,12 @@ class PARQUET_EXPORT Statistics {
   /// \brief The number of distinct values, may not be set
   virtual int64_t distinct_count() const = 0;
 
+  /// \brief Return true if the count of NaN values is set
+  virtual bool HasNanCount() const = 0;

Review Comment:
   Why not combine `HasNanCount` and `nan_count` just like `std::optional<bool> 
is_min_value_exact()` does?



##########
cpp/src/parquet/file_serialize_test.cc:
##########
@@ -480,6 +482,67 @@ TEST(ParquetRoundtrip, AllNulls) {
   EXPECT_THAT(def_levels, ElementsAre(0, 0, 0));
 }
 
+TEST(TestFileWriter, FloatingPointColumnOrder) {
+  schema::NodeVector fields{
+      schema::Float("float", Repetition::REQUIRED),
+      schema::Double("double", Repetition::REQUIRED),
+      schema::PrimitiveNode::Make("float16", Repetition::REQUIRED, 
LogicalType::Float16(),
+                                  Type::FIXED_LEN_BYTE_ARRAY, 2),
+      schema::Int32("int", Repetition::REQUIRED)};
+
+  auto schema = std::static_pointer_cast<GroupNode>(
+      GroupNode::Make("schema", Repetition::REQUIRED, fields));
+
+  auto assert_type_lengths = [](const SchemaDescriptor* schema) {
+    ASSERT_EQ(-1, schema->Column(0)->type_length());
+    ASSERT_EQ(-1, schema->Column(1)->type_length());
+    ASSERT_EQ(2, schema->Column(2)->type_length());
+    ASSERT_EQ(-1, schema->Column(3)->type_length());
+  };
+
+  auto write_orders = [&](ColumnOrder::type order) {
+    auto properties =
+        
WriterProperties::Builder().floating_point_column_order(order)->build();
+    auto sink = CreateOutputStream();
+    auto writer = ParquetFileWriter::Open(sink, schema, properties);
+    assert_type_lengths(writer->schema());
+    writer->Close();
+    auto writer_metadata = writer->metadata();
+    PARQUET_ASSIGN_OR_THROW(auto buffer, sink->Finish());
+    auto file_metadata =
+        ParquetFileReader::Open(
+            std::make_shared<::arrow::io::BufferReader>(std::move(buffer)))
+            ->metadata();
+    return std::pair{std::move(writer_metadata), std::move(file_metadata)};
+  };
+
+  auto assert_orders = [](const SchemaDescriptor* schema,
+                          ColumnOrder::type floating_point_order) {
+    for (int column_index = 0; column_index < 3; ++column_index) {
+      ASSERT_EQ(floating_point_order,
+                schema->Column(column_index)->column_order().get_order());
+    }
+    ASSERT_EQ(ColumnOrder::TYPE_DEFINED_ORDER,
+              schema->Column(3)->column_order().get_order());
+  };
+
+  SchemaDescriptor input_schema;
+  input_schema.Init(schema);
+  assert_type_lengths(&input_schema);
+  assert_orders(&input_schema, ColumnOrder::TYPE_DEFINED_ORDER);
+
+  auto [ieee_writer, ieee_file] = 
write_orders(ColumnOrder::IEEE_754_TOTAL_ORDER);
+  assert_orders(&input_schema, ColumnOrder::TYPE_DEFINED_ORDER);
+  assert_orders(ieee_writer->schema(), ColumnOrder::IEEE_754_TOTAL_ORDER);
+  assert_orders(ieee_file->schema(), ColumnOrder::IEEE_754_TOTAL_ORDER);
+
+  auto [type_writer, type_file] = 
write_orders(ColumnOrder::TYPE_DEFINED_ORDER);
+  assert_orders(&input_schema, ColumnOrder::TYPE_DEFINED_ORDER);
+  assert_orders(type_writer->schema(), ColumnOrder::TYPE_DEFINED_ORDER);
+  assert_orders(type_file->schema(), ColumnOrder::TYPE_DEFINED_ORDER);
+  EXPECT_THROW(ieee_file->AppendRowGroups(*type_file), ParquetException);

Review Comment:
   nit: check error message



##########
cpp/src/parquet/file_writer.cc:
##########
@@ -335,15 +338,46 @@ class RowGroupSerializer : public 
RowGroupWriter::Contents {
 // An implementation of ParquetFileWriter::Contents that deals with the Parquet
 // file structure, Thrift serialization, and other internal matters
 
+namespace {
+
+std::shared_ptr<GroupNode> MakeWriterSchema(const GroupNode& input_schema,

Review Comment:
   Should we remove this function? The `Open` function below just trust input 
`std::shared_ptr<GroupNode> schema` with all `type_length` and `column_order`. 
This provides the flexbility of low-level api to enable users to mix ieee754 
and type_defined order for different floating point types.
   
   Instead, we can move `floating_point_column_order()` to 
`ArrowWriterProperties` so we are converting Arrow schema to parquet GroupNode 
with expected column order at all once.



##########
cpp/src/parquet/page_index_test.cc:
##########
@@ -506,6 +509,9 @@ void TestWriteTypedColumnIndex(schema::NodePtr node,
                                int16_t max_repetition_level = 0,
                                const std::vector<PageLevelHistogram>& 
page_levels = {}) {
   const bool build_size_stats = !page_levels.empty();
+  const bool has_nan_counts = std::all_of(

Review Comment:
   nit: I guess that you don't want to change the signature here but currently 
`has_nan_counts` and `has_null_counts` are handled differently. It would be 
more readable to use consistent approach to pass or compute both of them.



##########
cpp/src/parquet/encoding_test.cc:
##########
@@ -466,6 +469,70 @@ TEST(TestDictionaryEncoding, CannotDictDecodeBoolean) {
   ASSERT_THROW(MakeDictDecoder<BooleanType>(nullptr), ParquetException);
 }
 
+template <typename DType, typename UInt, size_t NumValues>
+void TestFloatingDictionaryBits(const std::array<UInt, NumValues>& bits,
+                                int num_entries) {
+  using T = typename DType::c_type;
+  std::array<T, NumValues> values;
+  std::transform(bits.begin(), bits.end(), values.begin(),
+                 [](UInt value) { return ::arrow::util::SafeCopy<T>(value); });
+
+  auto encoder = MakeTypedEncoder<DType>(Encoding::PLAIN, true);
+  auto dictionary = dynamic_cast<DictEncoder<DType>*>(encoder.get());
+  ASSERT_NE(nullptr, dictionary);
+  encoder->Put(values.data(), values.size());
+  ASSERT_EQ(num_entries, dictionary->num_entries());
+
+  auto buffer = AllocateBuffer(default_memory_pool(), 
dictionary->dict_encoded_size());
+  dictionary->WriteDict(buffer->mutable_data());
+  const UInt* encoded = reinterpret_cast<const UInt*>(buffer->data());
+  for (int value_index = 0; value_index < num_entries; ++value_index) {
+    EXPECT_EQ(bits[value_index], encoded[value_index]);
+  }
+
+  using ArrowType = std::conditional_t<std::is_same_v<DType, FloatType>,
+                                       ::arrow::FloatType, 
::arrow::DoubleType>;
+  typename ::arrow::TypeTraits<ArrowType>::BuilderType builder;
+  ASSERT_OK(
+      builder.AppendValues(std::vector<T>(values.begin(), values.begin() + 
num_entries)));
+  std::shared_ptr<::arrow::Array> values_array;
+  ASSERT_OK(builder.Finish(&values_array));
+  auto direct_encoder = MakeTypedEncoder<DType>(Encoding::PLAIN, true);
+  auto direct_dictionary = 
dynamic_cast<DictEncoder<DType>*>(direct_encoder.get());
+  ASSERT_NE(nullptr, direct_dictionary);
+  direct_dictionary->PutDictionary(*values_array);
+  ASSERT_EQ(num_entries, direct_dictionary->num_entries());
+  auto direct_buffer =
+      AllocateBuffer(default_memory_pool(), 
direct_dictionary->dict_encoded_size());
+  direct_dictionary->WriteDict(direct_buffer->mutable_data());
+  const UInt* direct_encoded = reinterpret_cast<const 
UInt*>(direct_buffer->data());
+  for (int value_index = 0; value_index < num_entries; ++value_index) {
+    EXPECT_EQ(bits[value_index], direct_encoded[value_index]);
+  }
+}
+
+TEST(TestDictionaryEncoding, FloatingPointBits) {

Review Comment:
   Do we need to test float16 here?



##########
cpp/src/parquet/file_serialize_test.cc:
##########
@@ -480,6 +482,67 @@ TEST(ParquetRoundtrip, AllNulls) {
   EXPECT_THAT(def_levels, ElementsAre(0, 0, 0));
 }
 
+TEST(TestFileWriter, FloatingPointColumnOrder) {
+  schema::NodeVector fields{
+      schema::Float("float", Repetition::REQUIRED),
+      schema::Double("double", Repetition::REQUIRED),
+      schema::PrimitiveNode::Make("float16", Repetition::REQUIRED, 
LogicalType::Float16(),
+                                  Type::FIXED_LEN_BYTE_ARRAY, 2),
+      schema::Int32("int", Repetition::REQUIRED)};
+
+  auto schema = std::static_pointer_cast<GroupNode>(
+      GroupNode::Make("schema", Repetition::REQUIRED, fields));
+
+  auto assert_type_lengths = [](const SchemaDescriptor* schema) {
+    ASSERT_EQ(-1, schema->Column(0)->type_length());
+    ASSERT_EQ(-1, schema->Column(1)->type_length());
+    ASSERT_EQ(2, schema->Column(2)->type_length());
+    ASSERT_EQ(-1, schema->Column(3)->type_length());
+  };
+
+  auto write_orders = [&](ColumnOrder::type order) {
+    auto properties =
+        
WriterProperties::Builder().floating_point_column_order(order)->build();
+    auto sink = CreateOutputStream();
+    auto writer = ParquetFileWriter::Open(sink, schema, properties);
+    assert_type_lengths(writer->schema());
+    writer->Close();
+    auto writer_metadata = writer->metadata();
+    PARQUET_ASSIGN_OR_THROW(auto buffer, sink->Finish());
+    auto file_metadata =
+        ParquetFileReader::Open(
+            std::make_shared<::arrow::io::BufferReader>(std::move(buffer)))
+            ->metadata();
+    return std::pair{std::move(writer_metadata), std::move(file_metadata)};
+  };
+
+  auto assert_orders = [](const SchemaDescriptor* schema,
+                          ColumnOrder::type floating_point_order) {
+    for (int column_index = 0; column_index < 3; ++column_index) {
+      ASSERT_EQ(floating_point_order,
+                schema->Column(column_index)->column_order().get_order());
+    }
+    ASSERT_EQ(ColumnOrder::TYPE_DEFINED_ORDER,
+              schema->Column(3)->column_order().get_order());
+  };
+
+  SchemaDescriptor input_schema;
+  input_schema.Init(schema);
+  assert_type_lengths(&input_schema);
+  assert_orders(&input_schema, ColumnOrder::TYPE_DEFINED_ORDER);
+
+  auto [ieee_writer, ieee_file] = 
write_orders(ColumnOrder::IEEE_754_TOTAL_ORDER);
+  assert_orders(&input_schema, ColumnOrder::TYPE_DEFINED_ORDER);

Review Comment:
   Here we test that input schema has not been altered?



-- 
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]

Reply via email to