Copilot commented on code in PR #13136:
URL: https://github.com/apache/gluten/pull/13136#discussion_r4405418867


##########
.github/workflows/util/delta-spark-ut/known-failures.txt:
##########
@@ -369,14 +369,10 @@ 
org.apache.spark.sql.delta.deletionvectors.DeletionVectorsWithPredicatePushdownS
 
org.apache.spark.sql.delta.deletionvectors.DeletionVectorsWithPredicatePushdownSuite#PredicatePushdown:
 Single deletion at the second row group. vectorizedReaderEnabled: true 
readColumnarBatchAsRows: true
 
org.apache.spark.sql.delta.deletionvectors.DeletionVectorsWithPredicatePushdownSuite#huge
 table: delete a small number of rows from tables of 2B rows with DVs
 
org.apache.spark.sql.delta.deletionvectors.DeletionVectorsWithPredicatePushdownSuite#huge
 table: read from tables of 2B rows with existing DV of many zeros
-org.apache.spark.sql.delta.generatedsuites.dataskipping.DataSkippingDeltaV1CheckpointV2JsonSuite#Data
 skipping handles aliasing for _metadata fields
-org.apache.spark.sql.delta.generatedsuites.dataskipping.DataSkippingDeltaV1CheckpointV2JsonSuite#Data
 skipping handles aliasing for _metadata fields - old behavior with DataFrame 
schema
 
org.apache.spark.sql.delta.generatedsuites.dataskipping.DataSkippingDeltaV1CheckpointV2JsonSuite#data
 skipping flags

Review Comment:
   The PR description says the 12 removed entries use renamed Delta 4.4.1 suite 
identities, and that the fixes were validated in the Delta v4.2.0 / Spark 4.1 
run, which predates the Delta upgrade. The diff only removes the existing 
`DataSkippingDeltaV1*` identities and does not show any renamed identities. 
Either the description or the removed identities are incorrect. Please confirm 
which Delta version these identities target and re-run the Delta suite on that 
version before removing the entries.



##########
cpp/velox/substrait/SubstraitToVeloxPlan.cc:
##########
@@ -1656,34 +1656,44 @@ core::PlanNodePtr 
SubstraitToVeloxPlanConverter::toVeloxPlan(const ::substrait::
   auto names = colNameList;
   auto types = veloxTypeList;
 
-  // The columns we project from the file.
+  // The logical scan schema includes columns supplied by the connector, not 
just the file.
   auto baseSchema = ROW(std::move(names), std::move(types));
-  // The columns present in the table, if not available default to the 
baseSchema.
-  auto tableSchema = splitInfo->tableSchema ? splitInfo->tableSchema : 
baseSchema;
 
-  // Build dataColumns from tableSchema, excluding partition columns.
   // HiveTableHandle::dataColumns() is used as fileSchema for the reader.
-  // Partition columns should not be validated against the file's physical 
types
-  // (their values come from the partition path, not from the file).
-  std::unordered_set<std::string> partitionColNames;
-  for (int idx = 0; idx < colNameList.size(); idx++) {
-    if (columnTypes[idx] == ColumnType::kPartitionKey) {
-      partitionColNames.insert(colNameList[idx]);
-    }
-  }
   RowTypePtr dataColumns;
-  if (partitionColNames.empty()) {
-    dataColumns = tableSchema;
-  } else {
+  if (!splitInfo->tableSchema) {
     std::vector<std::string> dataColNames;
     std::vector<TypePtr> dataColTypes;
-    for (int idx = 0; idx < tableSchema->size(); idx++) {
-      if (partitionColNames.find(tableSchema->nameOf(idx)) == 
partitionColNames.end()) {
-        dataColNames.push_back(tableSchema->nameOf(idx));
-        dataColTypes.push_back(tableSchema->childAt(idx));
+    for (int idx = 0; idx < colNameList.size(); idx++) {
+      if (columnTypes[idx] == ColumnType::kRegular) {
+        dataColNames.push_back(colNameList[idx]);
+        dataColTypes.push_back(veloxTypeList[idx]);
       }
     }
     dataColumns = ROW(std::move(dataColNames), std::move(dataColTypes));
+  } else {
+    // An explicit schema describes physical fields and their ordinals. A field
+    // must not be removed just because a synthetic output has the same name.
+    const auto& tableSchema = splitInfo->tableSchema;
+    std::unordered_set<std::string> partitionColNames;
+    for (int idx = 0; idx < colNameList.size(); idx++) {
+      if (columnTypes[idx] == ColumnType::kPartitionKey) {
+        partitionColNames.insert(colNameList[idx]);
+      }
+    }
+    if (partitionColNames.empty()) {
+      dataColumns = tableSchema;
+    } else {
+      std::vector<std::string> dataColNames;
+      std::vector<TypePtr> dataColTypes;
+      for (int idx = 0; idx < tableSchema->size(); idx++) {
+        if (partitionColNames.find(tableSchema->nameOf(idx)) == 
partitionColNames.end()) {
+          dataColNames.push_back(tableSchema->nameOf(idx));
+          dataColTypes.push_back(tableSchema->childAt(idx));
+        }
+      }
+      dataColumns = ROW(std::move(dataColNames), std::move(dataColTypes));
+    }

Review Comment:
   The comment on the explicit-schema branch says physical fields and their 
ordinals must be preserved, but this loop drops any field whose name matches a 
partition key. Removing a field from the middle of the schema shifts the 
ordinals of later fields, which breaks positional mapping 
(ColumnMappingMode::kPosition). Consider keeping every physical field in the 
explicit schema and only excluding partition keys in a way that does not change 
ordinals, such as marking them rather than removing them, or pass the 
partition-exclusion decision to the reader so the physical schema stays intact.



##########
cpp/velox/substrait/SubstraitToVeloxPlan.cc:
##########
@@ -1656,34 +1656,44 @@ core::PlanNodePtr 
SubstraitToVeloxPlanConverter::toVeloxPlan(const ::substrait::
   auto names = colNameList;
   auto types = veloxTypeList;
 
-  // The columns we project from the file.
+  // The logical scan schema includes columns supplied by the connector, not 
just the file.
   auto baseSchema = ROW(std::move(names), std::move(types));
-  // The columns present in the table, if not available default to the 
baseSchema.
-  auto tableSchema = splitInfo->tableSchema ? splitInfo->tableSchema : 
baseSchema;
 
-  // Build dataColumns from tableSchema, excluding partition columns.
   // HiveTableHandle::dataColumns() is used as fileSchema for the reader.
-  // Partition columns should not be validated against the file's physical 
types
-  // (their values come from the partition path, not from the file).
-  std::unordered_set<std::string> partitionColNames;
-  for (int idx = 0; idx < colNameList.size(); idx++) {
-    if (columnTypes[idx] == ColumnType::kPartitionKey) {
-      partitionColNames.insert(colNameList[idx]);
-    }
-  }
   RowTypePtr dataColumns;
-  if (partitionColNames.empty()) {
-    dataColumns = tableSchema;
-  } else {
+  if (!splitInfo->tableSchema) {
     std::vector<std::string> dataColNames;
     std::vector<TypePtr> dataColTypes;
-    for (int idx = 0; idx < tableSchema->size(); idx++) {
-      if (partitionColNames.find(tableSchema->nameOf(idx)) == 
partitionColNames.end()) {
-        dataColNames.push_back(tableSchema->nameOf(idx));
-        dataColTypes.push_back(tableSchema->childAt(idx));
+    for (int idx = 0; idx < colNameList.size(); idx++) {
+      if (columnTypes[idx] == ColumnType::kRegular) {

Review Comment:
   The two branches each build dataColumns with a separate loop and similar 
name and type collection logic. Consider a small helper, such as one that takes 
a predicate over (name, type, index), so the filtering rules for regular and 
partition columns live in one place and future column types only need to be 
handled once.



##########
cpp/velox/tests/Substrait2VeloxPlanConversionTest.cc:
##########
@@ -75,12 +93,89 @@ class Substrait2VeloxPlanConversionTest : public 
exec::test::HiveConnectorTestBa
       if (splitInfo->columnMappingMode.has_value()) {
         splitBuilder.columnMappingMode(*splitInfo->columnMappingMode);
       }
+      for (const auto& [name, value] : splitInfo->metadataColumns.at(i)) {
+        splitBuilder.infoColumn(name, value);
+      }
       auto split = splitBuilder.build();
+      EXPECT_EQ(split->columnMappingMode, splitInfo->columnMappingMode);
       splits.emplace_back(split);
     }
     return splits;
   }
 
+  ::substrait::ReadRel makeRead(
+      const RowTypePtr& type,
+      const std::vector<::substrait::NamedStruct::ColumnType>& columnTypes = 
{}) {
+    google::protobuf::Arena arena;
+    VeloxToSubstraitTypeConvertor typeConverter;
+    ::substrait::ReadRel read;
+    read.mutable_common()->mutable_direct();
+    
read.mutable_base_schema()->CopyFrom(typeConverter.toSubstraitNamedStruct(arena,
 type));
+    for (auto columnType : columnTypes) {
+      read.mutable_base_schema()->add_column_types(columnType);
+    }
+    return read;
+  }
+
+  ::substrait::ReadRel_LocalFiles makeParquetFiles(
+      const std::vector<std::string>& paths,
+      const RowTypePtr& tableSchema = nullptr,
+      dwio::common::ColumnMappingMode columnMappingMode = 
dwio::common::ColumnMappingMode::kName) {
+    ::substrait::ReadRel_LocalFiles files;
+    google::protobuf::Arena arena;
+    VeloxToSubstraitTypeConvertor typeConverter;
+    for (const auto& path : paths) {
+      auto* file = files.add_items();
+      file->set_uri_file(path);
+      file->set_length(std::numeric_limits<uint64_t>::max());

Review Comment:
   Setting the file length to the maximum `uint64_t` value is an unexplained 
magic value. Add a comment describing what it means, such as 'length unknown, 
let the reader determine it', or use a named constant so that future readers do 
not assume it is a real file size.



##########
cpp/velox/tests/Substrait2VeloxPlanConversionTest.cc:
##########
@@ -75,12 +93,89 @@ class Substrait2VeloxPlanConversionTest : public 
exec::test::HiveConnectorTestBa
       if (splitInfo->columnMappingMode.has_value()) {
         splitBuilder.columnMappingMode(*splitInfo->columnMappingMode);
       }
+      for (const auto& [name, value] : splitInfo->metadataColumns.at(i)) {
+        splitBuilder.infoColumn(name, value);
+      }
       auto split = splitBuilder.build();
+      EXPECT_EQ(split->columnMappingMode, splitInfo->columnMappingMode);
       splits.emplace_back(split);
     }
     return splits;
   }
 
+  ::substrait::ReadRel makeRead(
+      const RowTypePtr& type,
+      const std::vector<::substrait::NamedStruct::ColumnType>& columnTypes = 
{}) {
+    google::protobuf::Arena arena;
+    VeloxToSubstraitTypeConvertor typeConverter;
+    ::substrait::ReadRel read;
+    read.mutable_common()->mutable_direct();
+    
read.mutable_base_schema()->CopyFrom(typeConverter.toSubstraitNamedStruct(arena,
 type));
+    for (auto columnType : columnTypes) {

Review Comment:
   The loop variable copies each element of the `columnTypes` vector. Iterate 
by const reference (`for (const auto& columnType : columnTypes)`) to avoid 
unnecessary copies.



##########
gluten-ut/spark41/src/test/scala/org/apache/spark/sql/execution/datasources/GlutenFileMetadataStructSuite.scala:
##########
@@ -97,6 +150,219 @@ class GlutenFileMetadataStructSuite extends 
FileMetadataStructSuite with GlutenS
     assert(executedPlan.exists(plan => plan.getClass == tag.runtimeClass))
   }
 
+  private def checkMetadataAnswer(
+      df: DataFrame,
+      expected: Seq[Row],
+      hasRowIndex: Boolean = false): Unit = {
+    checkAnswer(df, expected)
+    val settings = BackendsApiManager.getSettings
+    if (
+      settings.supportNativeMetadataColumns() &&
+      (!hasRowIndex || settings.supportNativeRowIndexColumn())
+    ) {
+      checkOperatorMatch[FileSourceScanExecTransformer](df)
+    } else {
+      checkOperatorMatch[FileSourceScanExec](df)
+    }
+  }
+
+  metadataColumns.zipWithIndex.foreach {
+    case (metadataColumn, index) =>
+      metadataColumnsNativeTest(
+        s"incompatible same-name data field for $metadataColumn",
+        schemaWithMetadataCollisions,
+        collisionRows0,
+        collisionRows1) {
+        (df, f0, f1) =>
+          val expected = 
Seq.fill(collisionRows0.size)(Row(f0(metadataColumn))) ++
+            Seq.fill(collisionRows1.size)(Row(f1(metadataColumn)))
+          checkMetadataAnswer(df.select(metadataColumn), expected)
+          
checkMetadataAnswer(df.select(Column(metadataColumn).as("aliased_metadata")), 
expected)
+
+          val combined = df.select(
+            Column(metadataColumn.stripPrefix("_metadata.")).as("user_value"),
+            Column(metadataColumn).as("metadata_value"))
+          checkAnswer(
+            combined,
+            collisionRows0.map(row => Row(row.get(index), f0(metadataColumn))) 
++
+              collisionRows1.map(row => Row(row.get(index), 
f1(metadataColumn))))
+          checkOperatorMatch[FileSourceScanExec](combined)
+      }
+  }
+
+  metadataColumnsNativeTest(
+    "metadata-only projection of empty Parquet files",
+    schemaWithMetadataCollisions,
+    Seq.empty,
+    Seq.empty) {
+    (df, _, _) =>
+      assert(df.inputFiles.length == 2)
+      df.inputFiles.foreach {
+        file =>
+          val input =
+            HadoopInputFile.fromPath(new Path(new URI(file)), 
spark.sessionState.newHadoopConf())
+          Utils.tryWithResource(ParquetFileReader.open(input)) {
+            reader =>
+              val footer = reader.getFooter
+              assert(footer.getBlocks.asScala.map(_.getRowCount).sum == 0L)
+              assert(
+                footer.getFileMetaData.getSchema
+                  .asGroupType()
+                  .getType("file_name")
+                  .asPrimitiveType()
+                  .getPrimitiveTypeName == PrimitiveTypeName.INT64)
+          }
+      }
+      checkMetadataAnswer(df.select(METADATA_FILE_NAME), Seq.empty)
+  }
+
+  metadataColumnsNativeTest(
+    "metadata constants with a regular sibling",
+    schemaWithMetadataCollisions,
+    collisionRows0,
+    collisionRows1) {
+    (df, f0, f1) =>
+      val selected = df.select((metadataColumns.map(Column(_)) :+ 
Column("id")): _*)
+      val expected = Seq((collisionRows0, f0), (collisionRows1, f1)).flatMap {
+        case (rows, metadata) =>
+          rows.map(row => Row.fromSeq(metadataColumns.map(metadata) :+ 
row.getLong(6)))

Review Comment:
   The index 6 is an unexplained magic number for the `id` column. It is 
repeated in several tests (for example lines 267, 268, 287, 288, 338, 339), so 
a change to the fixture layout would silently break them. Define a named 
constant such as `IdColumnIndex` (or look up the value by position from 
`schemaWithMetadataCollisions`) and use it in each place.



##########
gluten-ut/spark41/src/test/scala/org/apache/spark/sql/execution/datasources/GlutenFileMetadataStructSuite.scala:
##########
@@ -86,7 +136,10 @@ class GlutenFileMetadataStructSuite extends 
FileMetadataStructSuite with GlutenS
                 .listFiles()
                 .filter(_.getName.endsWith(s".$testFileFormat"))
                 .head
-              f(df, getMetadataForFile(realF0), getMetadataForFile(realF1))
+              assert(realF0.length() < 1024 * 1024 && realF1.length() < 1024 * 
1024)
+              withSQLConf(SQLConf.FILES_MAX_PARTITION_BYTES.key -> "1m") {

Review Comment:
   The 1 MB limit is repeated as a literal in two forms (`1024 * 1024` and 
`\"1m\"`). They must stay in sync for the test to be meaningful, but nothing 
links them. Extract a single constant, for example `MaxSplitBytes`, and derive 
the config string and the assertion from it.



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

Reply via email to