This is an automated email from the ASF dual-hosted git repository.

zhouyuan pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gluten.git


The following commit(s) were added to refs/heads/main by this push:
     new af7b9dd156 [VL] feat(iceberg): Add V3 initial value reads (#12537)
af7b9dd156 is described below

commit af7b9dd15658faa883ac8de4fa1c9c0ddd9f6d14
Author: inf <[email protected]>
AuthorDate: Wed Aug 19 13:21:15 2026 +0000

    [VL] feat(iceberg): Add V3 initial value reads (#12537)
---
 .../gluten/execution/VeloxIcebergSuite.scala       | 50 +++++++++++++++++++++-
 .../apache/gluten/proto/IcebergReadExtension.proto | 24 +++++++++++
 .../gluten/backendsapi/velox/VeloxBackend.scala    |  2 +
 .../backendsapi/velox/VeloxTransformerApi.scala    | 30 ++++++++++++-
 cpp/velox/compute/VeloxPlanConverter.cc            |  9 ++--
 cpp/velox/compute/iceberg/IcebergPlanConverter.cc  | 26 +++++++++++
 cpp/velox/compute/iceberg/IcebergPlanConverter.h   | 11 +++++
 cpp/velox/substrait/SubstraitToVeloxPlan.cc        | 35 ++++++++++++++-
 .../org/apache/gluten/IcebergDefaultValueUtil.java | 12 +++++-
 .../org/apache/gluten/IcebergDefaultValueUtil.java | 12 +++++-
 .../org/apache/gluten/IcebergDefaultValueUtil.java | 12 +++++-
 .../substrait/rel/IcebergLocalFilesBuilder.java    |  8 +++-
 .../substrait/rel/IcebergLocalFilesNode.java       | 29 ++++++++++++-
 .../gluten/execution/IcebergScanTransformer.scala  | 24 ++++++++++-
 .../spark/source/GlutenIcebergSourceUtil.scala     | 41 +++++++++++++++++-
 .../rel/IcebergLocalFilesNodeBoundsTest.java       |  4 +-
 .../gluten/backendsapi/BackendSettingsApi.scala    |  2 +
 .../apache/gluten/backendsapi/TransformerApi.scala |  7 +++
 18 files changed, 317 insertions(+), 21 deletions(-)

diff --git 
a/backends-velox/src-iceberg/test/scala/org/apache/gluten/execution/VeloxIcebergSuite.scala
 
b/backends-velox/src-iceberg/test/scala/org/apache/gluten/execution/VeloxIcebergSuite.scala
index edb30dac61..4e540c15af 100644
--- 
a/backends-velox/src-iceberg/test/scala/org/apache/gluten/execution/VeloxIcebergSuite.scala
+++ 
b/backends-velox/src-iceberg/test/scala/org/apache/gluten/execution/VeloxIcebergSuite.scala
@@ -16,4 +16,52 @@
  */
 package org.apache.gluten.execution
 
-class VeloxIcebergSuite extends IcebergSuite
+import org.apache.gluten.config.GlutenConfig
+
+import org.apache.spark.sql.Row
+import org.apache.spark.sql.connector.catalog.{Identifier, TableCatalog}
+
+import org.apache.iceberg.UpdateSchema
+import org.apache.iceberg.expressions.Literal
+import org.apache.iceberg.spark.source.SparkTable
+import org.apache.iceberg.types.{Type, Types}
+
+class VeloxIcebergSuite extends IcebergSuite {
+  testWithMinSparkVersion("iceberg v3 initial default for an added column", 
"3.4") {
+    withTable("iceberg_v3_initial_default") {
+      withSQLConf(GlutenConfig.GLUTEN_ENABLED.key -> "false") {
+        spark.sql("""
+                    |CREATE TABLE iceberg_v3_initial_default (id INT)
+                    |USING iceberg
+                    |TBLPROPERTIES ('format-version' = '3')
+                    |""".stripMargin)
+        spark.sql("INSERT INTO iceberg_v3_initial_default VALUES (1), (2)")
+
+        val catalog = spark.sessionState.catalogManager
+          .catalog("spark_catalog")
+          .asInstanceOf[TableCatalog]
+        val updateSchema = catalog
+          .loadTable(Identifier.of(Array("default"), 
"iceberg_v3_initial_default"))
+          .asInstanceOf[SparkTable]
+          .table()
+          .updateSchema()
+        classOf[UpdateSchema]
+          .getMethod(
+            "addColumn",
+            classOf[String],
+            classOf[Type],
+            classOf[Literal[_]])
+          .invoke(updateSchema, "country", Types.StringType.get(), 
Literal.of("IN"))
+        updateSchema.commit()
+        spark.catalog.refreshTable("iceberg_v3_initial_default")
+      }
+
+      runQueryAndCompare(
+        "SELECT id, country FROM iceberg_v3_initial_default ORDER BY id") {
+        df =>
+          checkAnswer(df, Seq(Row(1, "IN"), Row(2, "IN")))
+          checkGlutenPlan[IcebergScanTransformer](df)
+      }
+    }
+  }
+}
diff --git 
a/backends-velox/src/main/resources/org/apache/gluten/proto/IcebergReadExtension.proto
 
b/backends-velox/src/main/resources/org/apache/gluten/proto/IcebergReadExtension.proto
new file mode 100644
index 0000000000..2ea518dd6d
--- /dev/null
+++ 
b/backends-velox/src/main/resources/org/apache/gluten/proto/IcebergReadExtension.proto
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: Apache-2.0
+syntax = "proto3";
+
+package gluten;
+
+option java_package = "org.apache.gluten.proto";
+option java_multiple_files = true;
+
+// Iceberg-specific metadata carried in LocalFiles.advanced_extension.
+message IcebergReadExtension {
+  message ColumnFieldId {
+    string name = 1;
+    int32 field_id = 2;
+  }
+
+  message ColumnDefault {
+    string name = 1;
+    int32 field_id = 2;
+    string initial_default = 3;
+  }
+
+  repeated ColumnDefault column_defaults = 1;
+  repeated ColumnFieldId column_field_ids = 2;
+}
diff --git 
a/backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxBackend.scala
 
b/backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxBackend.scala
index 09deec69e4..27d1dc0a2d 100644
--- 
a/backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxBackend.scala
+++ 
b/backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxBackend.scala
@@ -571,6 +571,8 @@ object VeloxBackendSettings extends BackendSettingsApi {
 
   override def supportIcebergEqualityDeleteRead(): Boolean = false
 
+  override def supportIcebergInitialDefaultRead(): Boolean = true
+
   override def reorderColumnsForPartitionWrite(): Boolean = true
 
   override def enableEnhancedFeatures(): Boolean = 
VeloxConfig.get.enableEnhancedFeatures()
diff --git 
a/backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxTransformerApi.scala
 
b/backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxTransformerApi.scala
index 47ad778dc8..04df524427 100644
--- 
a/backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxTransformerApi.scala
+++ 
b/backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxTransformerApi.scala
@@ -21,7 +21,7 @@ import org.apache.gluten.exception.GlutenException
 import org.apache.gluten.execution.WriteFilesExecTransformer
 import org.apache.gluten.execution.datasource.GlutenFormatFactory
 import org.apache.gluten.expression.ConverterUtils
-import org.apache.gluten.proto.ConfigMap
+import org.apache.gluten.proto.{ConfigMap, IcebergReadExtension}
 import org.apache.gluten.runtime.Runtimes
 import org.apache.gluten.substrait.SubstraitContext
 import org.apache.gluten.substrait.expression.{ExpressionBuilder, 
ExpressionNode}
@@ -124,6 +124,34 @@ class VeloxTransformerApi extends TransformerApi with 
Logging {
 
   override def packPBMessage(message: Message): Any = Any.pack(message, "")
 
+  override def packIcebergReadExtension(
+      fieldIds: JMap[String, Integer],
+      initialDefaults: JMap[String, String]): Any = {
+    val extensionBuilder = IcebergReadExtension.newBuilder()
+    fieldIds.asScala.toSeq.sortBy(_._1).foreach {
+      case (name, fieldId) =>
+        extensionBuilder.addColumnFieldIds(
+          IcebergReadExtension.ColumnFieldId
+            .newBuilder()
+            .setName(name)
+            .setFieldId(fieldId))
+    }
+    initialDefaults.asScala.toSeq.sortBy(_._1).foreach {
+      case (name, initialDefault) =>
+        val fieldId = fieldIds.get(name)
+        if (fieldId == null) {
+          throw new IllegalArgumentException(s"Missing Iceberg field ID for 
column $name")
+        }
+        extensionBuilder.addColumnDefaults(
+          IcebergReadExtension.ColumnDefault
+            .newBuilder()
+            .setName(name)
+            .setFieldId(fieldId)
+            .setInitialDefault(initialDefault))
+    }
+    packPBMessage(extensionBuilder.build())
+  }
+
   override def invalidateSQLExecutionResource(executionId: String): Unit = {
     GlutenDriverEndpoint.invalidateResourceRelation(executionId)
   }
diff --git a/cpp/velox/compute/VeloxPlanConverter.cc 
b/cpp/velox/compute/VeloxPlanConverter.cc
index 22a7c49ac3..82c1f29036 100644
--- a/cpp/velox/compute/VeloxPlanConverter.cc
+++ b/cpp/velox/compute/VeloxPlanConverter.cc
@@ -137,9 +137,10 @@ std::shared_ptr<DeltaSplitInfo> parseDeltaSplitInfo(
 
 std::shared_ptr<SplitInfo> parseScanSplitInfo(
     const facebook::velox::config::ConfigBase* veloxCfg,
-    const 
google::protobuf::RepeatedPtrField<substrait::ReadRel_LocalFiles_FileOrFiles>& 
fileList) {
+    const substrait::ReadRel_LocalFiles& localFiles) {
   using SubstraitFileFormatCase = 
::substrait::ReadRel_LocalFiles_FileOrFiles::FileFormatCase;
 
+  const auto& fileList = localFiles.items();
   auto splitInfo = std::make_shared<SplitInfo>();
   splitInfo->leafType = SplitInfo::LeafType::TABLE_SCAN;
   splitInfo->paths.reserve(fileList.size());
@@ -193,7 +194,8 @@ std::shared_ptr<SplitInfo> parseScanSplitInfo(
         splitInfo->format = dwio::common::FileFormat::TEXT;
         break;
       case SubstraitFileFormatCase::kIceberg:
-        splitInfo = IcebergPlanConverter::parseIcebergSplitInfo(file, 
std::move(splitInfo));
+        splitInfo =
+            IcebergPlanConverter::parseIcebergSplitInfo(file, 
localFiles.advanced_extension(), std::move(splitInfo));
         break;
       case SubstraitFileFormatCase::kDelta:
         splitInfo = parseDeltaSplitInfo(file, std::move(splitInfo));
@@ -237,8 +239,7 @@ void parseLocalFileNodes(
   std::vector<std::shared_ptr<SplitInfo>> splitInfos;
   splitInfos.reserve(localFiles.size());
   for (const auto& localFile : localFiles) {
-    const auto& fileList = localFile.items();
-    splitInfos.push_back(parseScanSplitInfo(veloxCfg, fileList));
+    splitInfos.push_back(parseScanSplitInfo(veloxCfg, localFile));
   }
 
   planConverter->setSplitInfos(std::move(splitInfos));
diff --git a/cpp/velox/compute/iceberg/IcebergPlanConverter.cc 
b/cpp/velox/compute/iceberg/IcebergPlanConverter.cc
index 5212381623..097c984357 100644
--- a/cpp/velox/compute/iceberg/IcebergPlanConverter.cc
+++ b/cpp/velox/compute/iceberg/IcebergPlanConverter.cc
@@ -17,6 +17,8 @@
 
 #include "IcebergPlanConverter.h"
 
+#include "IcebergReadExtension.pb.h"
+
 namespace gluten {
 
 namespace {
@@ -39,6 +41,7 @@ std::unordered_map<int32_t, std::string> parseBounds(const 
SubstraitDeleteBounds
 
 std::shared_ptr<IcebergSplitInfo> IcebergPlanConverter::parseIcebergSplitInfo(
     substrait::ReadRel_LocalFiles_FileOrFiles file,
+    const substrait::extensions::AdvancedExtension& extension,
     std::shared_ptr<SplitInfo> splitInfo) {
   using SubstraitFileFormatCase = 
::substrait::ReadRel_LocalFiles_FileOrFiles::IcebergReadOptions::FileFormatCase;
   using SubstraitDeleteFileFormatCase =
@@ -58,6 +61,29 @@ std::shared_ptr<IcebergSplitInfo> 
IcebergPlanConverter::parseIcebergSplitInfo(
       icebergSplitInfo->format = dwio::common::FileFormat::UNKNOWN;
       break;
   }
+
+  if (icebergSplitInfo->columns.empty() && extension.has_enhancement() &&
+      extension.enhancement().Is<::gluten::IcebergReadExtension>()) {
+    ::gluten::IcebergReadExtension icebergExtension;
+    VELOX_USER_CHECK(extension.enhancement().UnpackTo(&icebergExtension), 
"Failed to unpack Iceberg read extension");
+    for (const auto& column : icebergExtension.column_field_ids()) {
+      auto [it, inserted] =
+          icebergSplitInfo->columns.try_emplace(column.name(), 
IcebergColumnInfo{column.field_id(), std::nullopt});
+      if (!inserted) {
+        VELOX_USER_CHECK_EQ(
+            it->second.fieldId, column.field_id(), "Conflicting Iceberg field 
IDs for column '{}'", column.name());
+      }
+    }
+    for (const auto& column : icebergExtension.column_defaults()) {
+      auto [it, inserted] = icebergSplitInfo->columns.try_emplace(
+          column.name(), IcebergColumnInfo{column.field_id(), 
column.initial_default()});
+      if (!inserted) {
+        VELOX_USER_CHECK_EQ(
+            it->second.fieldId, column.field_id(), "Conflicting Iceberg field 
IDs for column '{}'", column.name());
+        it->second.initialDefault = column.initial_default();
+      }
+    }
+  }
   if (icebergReadOption.delete_files_size() > 0) {
     auto deleteFiles = icebergReadOption.delete_files();
     std::vector<IcebergDeleteFile> deletes;
diff --git a/cpp/velox/compute/iceberg/IcebergPlanConverter.h 
b/cpp/velox/compute/iceberg/IcebergPlanConverter.h
index d634a861fe..af2631200e 100644
--- a/cpp/velox/compute/iceberg/IcebergPlanConverter.h
+++ b/cpp/velox/compute/iceberg/IcebergPlanConverter.h
@@ -17,14 +17,24 @@
 
 #pragma once
 
+#include <optional>
+#include <string>
+#include <unordered_map>
+
 #include "substrait/SubstraitToVeloxPlan.h"
 #include "velox/connectors/hive/iceberg/IcebergDeleteFile.h"
 
 using namespace facebook::velox::connector::hive::iceberg;
 
 namespace gluten {
+struct IcebergColumnInfo {
+  int32_t fieldId;
+  std::optional<std::string> initialDefault;
+};
+
 struct IcebergSplitInfo : SplitInfo {
   std::vector<std::vector<IcebergDeleteFile>> deleteFilesVec;
+  std::unordered_map<std::string, IcebergColumnInfo> columns;
 
   IcebergSplitInfo(const SplitInfo& splitInfo) : SplitInfo(splitInfo) {
     // Reserve the actual size of the deleteFilesVec.
@@ -36,6 +46,7 @@ class IcebergPlanConverter {
  public:
   static std::shared_ptr<IcebergSplitInfo> parseIcebergSplitInfo(
       substrait::ReadRel_LocalFiles_FileOrFiles file,
+      const substrait::extensions::AdvancedExtension& extension,
       std::shared_ptr<SplitInfo> splitInfo);
 };
 
diff --git a/cpp/velox/substrait/SubstraitToVeloxPlan.cc 
b/cpp/velox/substrait/SubstraitToVeloxPlan.cc
index 9e85daa206..97324de4e1 100644
--- a/cpp/velox/substrait/SubstraitToVeloxPlan.cc
+++ b/cpp/velox/substrait/SubstraitToVeloxPlan.cc
@@ -26,6 +26,7 @@
 #include "operators/hashjoin/HashTableBuilder.h"
 #include "operators/plannodes/RowVectorStream.h"
 #include "velox/connectors/hive/HiveDataSink.h"
+#include "velox/connectors/hive/iceberg/IcebergColumnHandle.h"
 #include "velox/exec/TableWriter.h"
 #include "velox/type/Type.h"
 
@@ -1609,11 +1610,41 @@ core::PlanNodePtr 
SubstraitToVeloxPlanConverter::toVeloxPlan(const ::substrait::
   std::vector<std::string> outNames;
   outNames.reserve(colNameList.size());
   connector::ColumnHandleMap assignments;
+  const auto icebergSplitInfo = 
std::dynamic_pointer_cast<IcebergSplitInfo>(splitInfo);
   for (int idx = 0; idx < colNameList.size(); idx++) {
     auto outName = SubstraitParser::makeNodeName(planNodeId_, idx);
     auto columnType = columnTypes[idx];
-    assignments[outName] = std::make_shared<connector::hive::HiveColumnHandle>(
-        colNameList[idx], columnType, veloxTypeList[idx], veloxTypeList[idx]);
+    const IcebergColumnInfo* icebergColumn = nullptr;
+    if (icebergSplitInfo) {
+      auto columnIt = icebergSplitInfo->columns.find(colNameList[idx]);
+      if (columnIt != icebergSplitInfo->columns.end()) {
+        icebergColumn = &columnIt->second;
+      } else if (asLowerCase) {
+        for (const auto& [name, column] : icebergSplitInfo->columns) {
+          auto normalizedName = name;
+          folly::toLowerAscii(normalizedName);
+          if (normalizedName == colNameList[idx]) {
+            icebergColumn = &column;
+            break;
+          }
+        }
+      }
+    }
+    // Gluten serializes Iceberg partition dates as ISO strings. Use Iceberg
+    // handles only for regular columns so all data columns are mapped by field
+    // ID together, while partition columns keep the existing Hive conversion.
+    if (icebergColumn && columnType == ColumnType::kRegular) {
+      assignments[outName] = 
std::make_shared<connector::hive::iceberg::IcebergColumnHandle>(
+          colNameList[idx],
+          columnType,
+          veloxTypeList[idx],
+          facebook::velox::parquet::ParquetFieldId(icebergColumn->fieldId),
+          std::vector<common::Subfield>{},
+          icebergColumn->initialDefault);
+    } else {
+      assignments[outName] = 
std::make_shared<connector::hive::HiveColumnHandle>(
+          colNameList[idx], columnType, veloxTypeList[idx], 
veloxTypeList[idx]);
+    }
     outNames.emplace_back(outName);
   }
   auto outputType = ROW(std::move(outNames), std::move(veloxTypeList));
diff --git 
a/backends-velox/src-iceberg/test/scala/org/apache/gluten/execution/VeloxIcebergSuite.scala
 
b/gluten-iceberg/src-iceberg10/main/java/org/apache/gluten/IcebergDefaultValueUtil.java
similarity index 75%
copy from 
backends-velox/src-iceberg/test/scala/org/apache/gluten/execution/VeloxIcebergSuite.scala
copy to 
gluten-iceberg/src-iceberg10/main/java/org/apache/gluten/IcebergDefaultValueUtil.java
index edb30dac61..0d50c905b8 100644
--- 
a/backends-velox/src-iceberg/test/scala/org/apache/gluten/execution/VeloxIcebergSuite.scala
+++ 
b/gluten-iceberg/src-iceberg10/main/java/org/apache/gluten/IcebergDefaultValueUtil.java
@@ -14,6 +14,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.gluten.execution
+package org.apache.gluten;
 
-class VeloxIcebergSuite extends IcebergSuite
+import org.apache.iceberg.types.Types;
+
+public final class IcebergDefaultValueUtil {
+  private IcebergDefaultValueUtil() {}
+
+  public static Object getInitialDefault(Types.NestedField field) {
+    return field.initialDefault();
+  }
+}
diff --git 
a/backends-velox/src-iceberg/test/scala/org/apache/gluten/execution/VeloxIcebergSuite.scala
 
b/gluten-iceberg/src-iceberg3/main/java/org/apache/gluten/IcebergDefaultValueUtil.java
similarity index 76%
copy from 
backends-velox/src-iceberg/test/scala/org/apache/gluten/execution/VeloxIcebergSuite.scala
copy to 
gluten-iceberg/src-iceberg3/main/java/org/apache/gluten/IcebergDefaultValueUtil.java
index edb30dac61..fff8cb444f 100644
--- 
a/backends-velox/src-iceberg/test/scala/org/apache/gluten/execution/VeloxIcebergSuite.scala
+++ 
b/gluten-iceberg/src-iceberg3/main/java/org/apache/gluten/IcebergDefaultValueUtil.java
@@ -14,6 +14,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.gluten.execution
+package org.apache.gluten;
 
-class VeloxIcebergSuite extends IcebergSuite
+import org.apache.iceberg.types.Types;
+
+public final class IcebergDefaultValueUtil {
+  private IcebergDefaultValueUtil() {}
+
+  public static Object getInitialDefault(Types.NestedField field) {
+    return null;
+  }
+}
diff --git 
a/backends-velox/src-iceberg/test/scala/org/apache/gluten/execution/VeloxIcebergSuite.scala
 
b/gluten-iceberg/src-iceberg5/main/java/org/apache/gluten/IcebergDefaultValueUtil.java
similarity index 76%
copy from 
backends-velox/src-iceberg/test/scala/org/apache/gluten/execution/VeloxIcebergSuite.scala
copy to 
gluten-iceberg/src-iceberg5/main/java/org/apache/gluten/IcebergDefaultValueUtil.java
index edb30dac61..fff8cb444f 100644
--- 
a/backends-velox/src-iceberg/test/scala/org/apache/gluten/execution/VeloxIcebergSuite.scala
+++ 
b/gluten-iceberg/src-iceberg5/main/java/org/apache/gluten/IcebergDefaultValueUtil.java
@@ -14,6 +14,14 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.gluten.execution
+package org.apache.gluten;
 
-class VeloxIcebergSuite extends IcebergSuite
+import org.apache.iceberg.types.Types;
+
+public final class IcebergDefaultValueUtil {
+  private IcebergDefaultValueUtil() {}
+
+  public static Object getInitialDefault(Types.NestedField field) {
+    return null;
+  }
+}
diff --git 
a/gluten-iceberg/src/main/java/org/apache/gluten/substrait/rel/IcebergLocalFilesBuilder.java
 
b/gluten-iceberg/src/main/java/org/apache/gluten/substrait/rel/IcebergLocalFilesBuilder.java
index eef98f7784..ca4597b48e 100644
--- 
a/gluten-iceberg/src/main/java/org/apache/gluten/substrait/rel/IcebergLocalFilesBuilder.java
+++ 
b/gluten-iceberg/src/main/java/org/apache/gluten/substrait/rel/IcebergLocalFilesBuilder.java
@@ -31,7 +31,9 @@ public class IcebergLocalFilesBuilder {
       LocalFilesNode.ReadFileFormat fileFormat,
       List<String> preferredLocations,
       List<List<DeleteFile>> deleteFilesList,
-      List<Map<String, String>> metadataColumns) {
+      List<Map<String, String>> metadataColumns,
+      Map<String, Integer> fieldIds,
+      Map<String, String> initialDefaults) {
     return new IcebergLocalFilesNode(
         index,
         paths,
@@ -41,6 +43,8 @@ public class IcebergLocalFilesBuilder {
         fileFormat,
         preferredLocations,
         deleteFilesList,
-        metadataColumns);
+        metadataColumns,
+        fieldIds,
+        initialDefaults);
   }
 }
diff --git 
a/gluten-iceberg/src/main/java/org/apache/gluten/substrait/rel/IcebergLocalFilesNode.java
 
b/gluten-iceberg/src/main/java/org/apache/gluten/substrait/rel/IcebergLocalFilesNode.java
index ac5841226a..74c589143c 100644
--- 
a/gluten-iceberg/src/main/java/org/apache/gluten/substrait/rel/IcebergLocalFilesNode.java
+++ 
b/gluten-iceberg/src/main/java/org/apache/gluten/substrait/rel/IcebergLocalFilesNode.java
@@ -16,6 +16,10 @@
  */
 package org.apache.gluten.substrait.rel;
 
+import org.apache.gluten.backendsapi.BackendsApiManager;
+
+import com.google.protobuf.Any;
+import io.substrait.proto.AdvancedExtension;
 import io.substrait.proto.ReadRel;
 import org.apache.iceberg.DeleteFile;
 import org.apache.iceberg.FileContent;
@@ -25,6 +29,8 @@ import java.util.*;
 
 public class IcebergLocalFilesNode extends LocalFilesNode {
   private final List<List<DeleteFile>> deleteFilesList;
+  private final Map<String, Integer> fieldIds;
+  private final Map<String, String> initialDefaults;
 
   IcebergLocalFilesNode(
       Integer index,
@@ -35,7 +41,9 @@ public class IcebergLocalFilesNode extends LocalFilesNode {
       ReadFileFormat fileFormat,
       List<String> preferredLocations,
       List<List<DeleteFile>> deleteFilesList,
-      List<Map<String, String>> metadataColumns) {
+      List<Map<String, String>> metadataColumns,
+      Map<String, Integer> fieldIds,
+      Map<String, String> initialDefaults) {
     super(
         index,
         paths,
@@ -50,6 +58,25 @@ public class IcebergLocalFilesNode extends LocalFilesNode {
         new HashMap<>(),
         new ArrayList<>());
     this.deleteFilesList = deleteFilesList;
+    this.fieldIds = fieldIds;
+    this.initialDefaults = initialDefaults;
+  }
+
+  @Override
+  public ReadRel.LocalFiles toProtobuf() {
+    ReadRel.LocalFiles localFiles = super.toProtobuf();
+    if (initialDefaults.isEmpty()) {
+      return localFiles;
+    }
+
+    Any extension =
+        BackendsApiManager.getTransformerApiInstance()
+            .packIcebergReadExtension(fieldIds, initialDefaults);
+
+    return localFiles
+        .toBuilder()
+        
.setAdvancedExtension(AdvancedExtension.newBuilder().setEnhancement(extension))
+        .build();
   }
 
   @Override
diff --git 
a/gluten-iceberg/src/main/scala/org/apache/gluten/execution/IcebergScanTransformer.scala
 
b/gluten-iceberg/src/main/scala/org/apache/gluten/execution/IcebergScanTransformer.scala
index cc665ba4eb..16018e086d 100644
--- 
a/gluten-iceberg/src/main/scala/org/apache/gluten/execution/IcebergScanTransformer.scala
+++ 
b/gluten-iceberg/src/main/scala/org/apache/gluten/execution/IcebergScanTransformer.scala
@@ -41,6 +41,7 @@ import org.apache.iceberg.types.{Type, Types}
 import org.apache.iceberg.types.Type.TypeID
 import org.apache.iceberg.types.Types.{ListType, MapType, NestedField}
 
+import java.util.{HashMap => JHashMap}
 import java.util.Locale
 
 case class IcebergScanTransformer(
@@ -65,6 +66,16 @@ case class IcebergScanTransformer(
   // So use Metric to get NumSplits, NumDeletes is not reported by native 
metric
   private val numSplits = SQLMetrics.createMetric(sparkContext, new 
NumSplits().description())
 
+  private lazy val icebergInitialDefaults =
+    GlutenIcebergSourceUtil.getInitialDefaults(scan)
+
+  private lazy val icebergFieldIds =
+    if (icebergInitialDefaults.isEmpty) {
+      new JHashMap[String, Integer]()
+    } else {
+      GlutenIcebergSourceUtil.getFieldIds(scan)
+    }
+
   override def withNewPushdownFilters(filters: Seq[Expression]): 
BatchScanExecTransformerBase = {
     this.copy(pushDownFilters = Some(filters))
   }
@@ -150,6 +161,12 @@ case class IcebergScanTransformer(
       return ValidationResult.succeeded
     }
     val metadata = baseTable.operations().current()
+    if (
+      !BackendsApiManager.getSettings.supportIcebergInitialDefaultRead() &&
+      !icebergInitialDefaults.isEmpty
+    ) {
+      return ValidationResult.failed("Iceberg initial-default reads are not 
supported")
+    }
     if (metadata.formatVersion() >= 3) {
       val hasUnsupportedDelete = finalPartitions.exists {
         case p: SparkDataSourceRDDPartition =>
@@ -208,7 +225,12 @@ case class IcebergScanTransformer(
       metadataColumnNames: Seq[String]): SplitInfo = {
     val splitInfo = partition match {
       case p: SparkDataSourceRDDPartition =>
-        GlutenIcebergSourceUtil.genSplitInfo(p, getPartitionSchema, 
metadataColumnNames)
+        GlutenIcebergSourceUtil.genSplitInfo(
+          p,
+          getPartitionSchema,
+          metadataColumnNames,
+          icebergFieldIds,
+          icebergInitialDefaults)
       case _ => throw new GlutenNotSupportException()
     }
     numSplits.add(splitInfo.asInstanceOf[LocalFilesNode].getPaths.size())
diff --git 
a/gluten-iceberg/src/main/scala/org/apache/iceberg/spark/source/GlutenIcebergSourceUtil.scala
 
b/gluten-iceberg/src/main/scala/org/apache/iceberg/spark/source/GlutenIcebergSourceUtil.scala
index 1a658a7892..db1bb024af 100644
--- 
a/gluten-iceberg/src/main/scala/org/apache/iceberg/spark/source/GlutenIcebergSourceUtil.scala
+++ 
b/gluten-iceberg/src/main/scala/org/apache/iceberg/spark/source/GlutenIcebergSourceUtil.scala
@@ -16,6 +16,7 @@
  */
 package org.apache.iceberg.spark.source
 
+import org.apache.gluten.IcebergDefaultValueUtil
 import org.apache.gluten.backendsapi.BackendsApiManager
 import org.apache.gluten.exception.GlutenNotSupportException
 import org.apache.gluten.execution.SparkDataSourceRDDPartition
@@ -57,7 +58,9 @@ object GlutenIcebergSourceUtil {
   def genSplitInfo(
       partition: SparkDataSourceRDDPartition,
       readPartitionSchema: StructType,
-      metadataColumnNames: Seq[String]): SplitInfo = {
+      metadataColumnNames: Seq[String],
+      fieldIds: JMap[String, Integer],
+      initialDefaults: JMap[String, String]): SplitInfo = {
     val paths = new JArrayList[String]()
     val starts = new JArrayList[JLong]()
     val lengths = new JArrayList[JLong]()
@@ -103,10 +106,44 @@ object GlutenIcebergSourceUtil {
         .toList
         .asJava,
       deleteFilesList,
-      metadataColumns
+      metadataColumns,
+      fieldIds,
+      initialDefaults
     )
   }
 
+  def getFieldIds(sparkScan: Scan): JHashMap[String, Integer] = {
+    val fieldIds = new JHashMap[String, Integer]()
+    sparkScan match {
+      case scan: SparkBatchQueryScan =>
+        scan.table().schema().columns().asScala.foreach {
+          field => fieldIds.put(field.name(), field.fieldId())
+        }
+      case _ =>
+        throw new GlutenNotSupportException("Only support iceberg 
SparkBatchQueryScan.")
+    }
+    fieldIds
+  }
+
+  def getInitialDefaults(sparkScan: Scan): JHashMap[String, String] = {
+    val initialDefaults = new JHashMap[String, String]()
+    sparkScan match {
+      case scan: SparkBatchQueryScan =>
+        scan.table().schema().columns().asScala.foreach {
+          field =>
+            val defaultValue = IcebergDefaultValueUtil.getInitialDefault(field)
+            if (defaultValue != null) {
+              initialDefaults.put(
+                field.name(),
+                TypeUtil.getPartitionValueString(field.`type`(), defaultValue))
+            }
+        }
+      case _ =>
+        throw new GlutenNotSupportException("Only support iceberg 
SparkBatchQueryScan.")
+    }
+    initialDefaults
+  }
+
   private def genMetadataColumns(
       metadataColumnNames: Seq[String],
       filePath: String,
diff --git 
a/gluten-iceberg/src/test/java/org/apache/gluten/substrait/rel/IcebergLocalFilesNodeBoundsTest.java
 
b/gluten-iceberg/src/test/java/org/apache/gluten/substrait/rel/IcebergLocalFilesNodeBoundsTest.java
index 01fc50035c..f4fff4d6c4 100644
--- 
a/gluten-iceberg/src/test/java/org/apache/gluten/substrait/rel/IcebergLocalFilesNodeBoundsTest.java
+++ 
b/gluten-iceberg/src/test/java/org/apache/gluten/substrait/rel/IcebergLocalFilesNodeBoundsTest.java
@@ -59,7 +59,9 @@ public class IcebergLocalFilesNodeBoundsTest {
             LocalFilesNode.ReadFileFormat.ParquetReadFormat,
             Collections.emptyList(),
             Collections.singletonList(Collections.singletonList(deleteFile)),
-            Collections.singletonList(Collections.emptyMap()));
+            Collections.singletonList(Collections.emptyMap()),
+            Collections.emptyMap(),
+            Collections.emptyMap());
 
     ReadRel.LocalFiles.FileOrFiles.Builder fileBuilder =
         ReadRel.LocalFiles.FileOrFiles.newBuilder();
diff --git 
a/gluten-substrait/src/main/scala/org/apache/gluten/backendsapi/BackendSettingsApi.scala
 
b/gluten-substrait/src/main/scala/org/apache/gluten/backendsapi/BackendSettingsApi.scala
index 85b146e195..cf2026af63 100644
--- 
a/gluten-substrait/src/main/scala/org/apache/gluten/backendsapi/BackendSettingsApi.scala
+++ 
b/gluten-substrait/src/main/scala/org/apache/gluten/backendsapi/BackendSettingsApi.scala
@@ -152,6 +152,8 @@ trait BackendSettingsApi {
 
   def supportIcebergEqualityDeleteRead(): Boolean = true
 
+  def supportIcebergInitialDefaultRead(): Boolean = false
+
   def reorderColumnsForPartitionWrite(): Boolean = false
 
   def enableEnhancedFeatures(): Boolean = false
diff --git 
a/gluten-substrait/src/main/scala/org/apache/gluten/backendsapi/TransformerApi.scala
 
b/gluten-substrait/src/main/scala/org/apache/gluten/backendsapi/TransformerApi.scala
index a855537664..c35a85398a 100644
--- 
a/gluten-substrait/src/main/scala/org/apache/gluten/backendsapi/TransformerApi.scala
+++ 
b/gluten-substrait/src/main/scala/org/apache/gluten/backendsapi/TransformerApi.scala
@@ -74,6 +74,13 @@ trait TransformerApi {
 
   def packPBMessage(message: Message): Any
 
+  /** Packs Iceberg column initial defaults into a backend-specific read 
extension. */
+  def packIcebergReadExtension(
+      fieldIds: util.Map[String, Integer],
+      initialDefaults: util.Map[String, String]): Any = {
+    throw new UnsupportedOperationException("Iceberg initial-default reads are 
not supported")
+  }
+
   /** This method is only used for CH backend tests */
   def invalidateSQLExecutionResource(executionId: String): Unit = {}
 


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to