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

lzljs3620320 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new 105e50bd0 [spark] Support partition and bucket metadata column (#4180)
105e50bd0 is described below

commit 105e50bd0b7a707e355355789582632b83b5ed15
Author: Xiduo You <[email protected]>
AuthorDate: Thu Sep 19 10:03:02 2024 +0800

    [spark] Support partition and bucket metadata column (#4180)
---
 .../org/apache/paimon/spark/SparkTypeUtils.java    | 10 +++++
 .../org/apache/paimon/spark/PaimonBaseScan.scala   |  4 +-
 .../paimon/spark/PaimonPartitionReader.scala       |  5 ++-
 .../paimon/spark/PaimonRecordReaderIterator.scala  | 26 ++++++++----
 .../scala/org/apache/paimon/spark/SparkTable.scala |  8 +++-
 .../paimon/spark/schema/PaimonMetadataColumn.scala | 33 ++++++++++-----
 .../apache/paimon/spark/sql/PaimonQueryTest.scala  | 47 +++++++++++++++++++++-
 7 files changed, 110 insertions(+), 23 deletions(-)

diff --git 
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/SparkTypeUtils.java
 
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/SparkTypeUtils.java
index 08fb2de32..bc5eed00e 100644
--- 
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/SparkTypeUtils.java
+++ 
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/SparkTypeUtils.java
@@ -19,6 +19,7 @@
 package org.apache.paimon.spark;
 
 import org.apache.paimon.spark.util.shim.TypeUtils;
+import org.apache.paimon.table.Table;
 import org.apache.paimon.types.ArrayType;
 import org.apache.paimon.types.BigIntType;
 import org.apache.paimon.types.BinaryType;
@@ -59,6 +60,15 @@ public class SparkTypeUtils {
 
     private SparkTypeUtils() {}
 
+    public static StructType toSparkPartitionType(Table table) {
+        int[] projections = 
table.rowType().getFieldIndices(table.partitionKeys());
+        List<DataField> partitionTypes = new ArrayList<>();
+        for (int i : projections) {
+            partitionTypes.add(table.rowType().getFields().get(i));
+        }
+        return (StructType) SparkTypeUtils.fromPaimonType(new RowType(false, 
partitionTypes));
+    }
+
     public static StructType fromPaimonRowType(RowType type) {
         return (StructType) fromPaimonType(type);
     }
diff --git 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonBaseScan.scala
 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonBaseScan.scala
index 2188879c7..cb43560dc 100644
--- 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonBaseScan.scala
+++ 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonBaseScan.scala
@@ -118,7 +118,9 @@ abstract class PaimonBaseScan(
   }
 
   override def toBatch: Batch = {
-    val metadataColumns = metadataFields.map(field => 
PaimonMetadataColumn.get(field.name))
+    val partitionType = SparkTypeUtils.toSparkPartitionType(table)
+    val metadataColumns =
+      metadataFields.map(field => PaimonMetadataColumn.get(field.name, 
partitionType))
     PaimonBatch(lazyInputPartitions, readBuilder, metadataColumns)
   }
 
diff --git 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonPartitionReader.scala
 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonPartitionReader.scala
index cea235120..fa9072df3 100644
--- 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonPartitionReader.scala
+++ 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonPartitionReader.scala
@@ -87,8 +87,9 @@ case class PaimonPartitionReader(
 
   private def readSplit(): PaimonRecordReaderIterator = {
     if (splits.hasNext) {
-      val reader = readFunc(splits.next())
-      PaimonRecordReaderIterator(reader, metadataColumns)
+      val split = splits.next();
+      val reader = readFunc(split)
+      PaimonRecordReaderIterator(reader, metadataColumns, split)
     } else {
       null
     }
diff --git 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonRecordReaderIterator.scala
 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonRecordReaderIterator.scala
index ce880e670..6d6c1ca47 100644
--- 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonRecordReaderIterator.scala
+++ 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonRecordReaderIterator.scala
@@ -22,6 +22,7 @@ import org.apache.paimon.data.{BinaryString, GenericRow, 
InternalRow => PaimonIn
 import org.apache.paimon.fs.Path
 import org.apache.paimon.reader.{FileRecordIterator, RecordReader}
 import org.apache.paimon.spark.schema.PaimonMetadataColumn
+import org.apache.paimon.table.source.{DataSplit, Split}
 import org.apache.paimon.utils.CloseableIterator
 
 import org.apache.spark.sql.PaimonUtils
@@ -30,7 +31,8 @@ import java.io.IOException
 
 case class PaimonRecordReaderIterator(
     reader: RecordReader[PaimonInternalRow],
-    metadataColumns: Seq[PaimonMetadataColumn])
+    metadataColumns: Seq[PaimonMetadataColumn],
+    split: Split)
   extends CloseableIterator[PaimonInternalRow] {
 
   private var lastFilePath: Path = _
@@ -44,6 +46,15 @@ case class PaimonRecordReaderIterator(
     GenericRow.of(Array.fill(metadataColumns.size)(null.asInstanceOf[AnyRef]): 
_*)
   private val joinedRow: JoinedRow = JoinedRow.join(null, metadataRow)
 
+  private def validateMetadataColumns(): Unit = {
+    if (needMetadata) {
+      if (!isFileRecordIterator || !split.isInstanceOf[DataSplit]) {
+        throw new RuntimeException(
+          "There need be FileRecordIterator when metadata columns are 
required.")
+      }
+    }
+  }
+
   override def hasNext: Boolean = {
     if (currentIterator == null) {
       false
@@ -98,10 +109,7 @@ case class PaimonRecordReaderIterator(
           val dataRow = currentIterator.next()
           if (dataRow != null) {
             if (needMetadata) {
-              if (!isFileRecordIterator) {
-                throw new RuntimeException(
-                  "There need be FileRecoredIterator when metadata columns are 
required.")
-              }
+              validateMetadataColumns()
               
updateMetadataRow(currentIterator.asInstanceOf[FileRecordIterator[PaimonInternalRow]])
               currentResult = joinedRow.replace(dataRow, metadataRow)
             } else {
@@ -133,10 +141,14 @@ case class PaimonRecordReaderIterator(
     metadataColumns.zipWithIndex.foreach {
       case (metadataColumn, index) =>
         metadataColumn.name match {
-          case PaimonMetadataColumn.ROW_INDEX.name =>
+          case PaimonMetadataColumn.ROW_INDEX_COLUMN =>
             metadataRow.setField(index, fileRecordIterator.returnedPosition())
-          case PaimonMetadataColumn.FILE_PATH.name =>
+          case PaimonMetadataColumn.FILE_PATH_COLUMN =>
             metadataRow.setField(index, 
BinaryString.fromString(lastFilePath.toUri.toString))
+          case PaimonMetadataColumn.PARTITION_COLUMN =>
+            metadataRow.setField(index, 
split.asInstanceOf[DataSplit].partition())
+          case PaimonMetadataColumn.BUCKET_COLUMN =>
+            metadataRow.setField(index, split.asInstanceOf[DataSplit].bucket())
         }
     }
   }
diff --git 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/SparkTable.scala
 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/SparkTable.scala
index dd24fc6b1..d06c125d2 100644
--- 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/SparkTable.scala
+++ 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/SparkTable.scala
@@ -81,7 +81,13 @@ case class SparkTable(table: Table)
   }
 
   override def metadataColumns: Array[MetadataColumn] = {
-    Array[MetadataColumn](PaimonMetadataColumn.FILE_PATH, 
PaimonMetadataColumn.ROW_INDEX)
+    val partitionType = SparkTypeUtils.toSparkPartitionType(table)
+    Array[MetadataColumn](
+      PaimonMetadataColumn.FILE_PATH,
+      PaimonMetadataColumn.ROW_INDEX,
+      PaimonMetadataColumn.PARTITION(partitionType),
+      PaimonMetadataColumn.BUCKET
+    )
   }
 
   override def newScanBuilder(options: CaseInsensitiveStringMap): ScanBuilder 
= {
diff --git 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/schema/PaimonMetadataColumn.scala
 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/schema/PaimonMetadataColumn.scala
index b539a1351..991f6f9c9 100644
--- 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/schema/PaimonMetadataColumn.scala
+++ 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/schema/PaimonMetadataColumn.scala
@@ -23,7 +23,7 @@ import org.apache.paimon.types.DataField
 
 import org.apache.spark.sql.catalyst.expressions.AttributeReference
 import org.apache.spark.sql.connector.catalog.MetadataColumn
-import org.apache.spark.sql.types.{DataType, LongType, StringType, StructField}
+import org.apache.spark.sql.types.{DataType, IntegerType, LongType, 
StringType, StructField, StructType}
 
 case class PaimonMetadataColumn(id: Int, override val name: String, override 
val dataType: DataType)
   extends MetadataColumn {
@@ -43,22 +43,35 @@ case class PaimonMetadataColumn(id: Int, override val name: 
String, override val
 
 object PaimonMetadataColumn {
 
-  val FILE_PATH_COLUMN = "__paimon_file_path"
   val ROW_INDEX_COLUMN = "__paimon_row_index"
-  val SUPPORTED_METADATA_COLUMNS: Seq[String] = Seq(FILE_PATH_COLUMN, 
ROW_INDEX_COLUMN)
+  val FILE_PATH_COLUMN = "__paimon_file_path"
+  val PARTITION_COLUMN = "__paimon_partition"
+  val BUCKET_COLUMN = "__paimon_bucket"
+  val SUPPORTED_METADATA_COLUMNS: Seq[String] = Seq(
+    ROW_INDEX_COLUMN,
+    FILE_PATH_COLUMN,
+    PARTITION_COLUMN,
+    BUCKET_COLUMN
+  )
 
   val ROW_INDEX: PaimonMetadataColumn =
     PaimonMetadataColumn(Int.MaxValue - 100, ROW_INDEX_COLUMN, LongType)
   val FILE_PATH: PaimonMetadataColumn =
     PaimonMetadataColumn(Int.MaxValue - 101, FILE_PATH_COLUMN, StringType)
+  val PARTITION: DataType => PaimonMetadataColumn = (dt: DataType) => {
+    PaimonMetadataColumn(Int.MaxValue - 102, PARTITION_COLUMN, dt)
+  }
+  val BUCKET: PaimonMetadataColumn =
+    PaimonMetadataColumn(Int.MaxValue - 103, BUCKET_COLUMN, IntegerType)
 
-  def get(metadataColumn: String): PaimonMetadataColumn = {
-    if (metadataColumn == FILE_PATH_COLUMN) {
-      FILE_PATH
-    } else if (metadataColumn == ROW_INDEX_COLUMN) {
-      ROW_INDEX
-    } else {
-      throw new IllegalArgumentException(s"$metadataColumn metadata column is 
not supported.")
+  def get(metadataColumn: String, partitionType: StructType): 
PaimonMetadataColumn = {
+    metadataColumn match {
+      case ROW_INDEX_COLUMN => ROW_INDEX
+      case FILE_PATH_COLUMN => FILE_PATH
+      case PARTITION_COLUMN => PARTITION(partitionType)
+      case BUCKET_COLUMN => BUCKET
+      case _ =>
+        throw new IllegalArgumentException(s"$metadataColumn metadata column 
is not supported.")
     }
   }
 }
diff --git 
a/paimon-spark/paimon-spark-common/src/test/scala/org/apache/paimon/spark/sql/PaimonQueryTest.scala
 
b/paimon-spark/paimon-spark-common/src/test/scala/org/apache/paimon/spark/sql/PaimonQueryTest.scala
index 702965701..c1814096f 100644
--- 
a/paimon-spark/paimon-spark-common/src/test/scala/org/apache/paimon/spark/sql/PaimonQueryTest.scala
+++ 
b/paimon-spark/paimon-spark-common/src/test/scala/org/apache/paimon/spark/sql/PaimonQueryTest.scala
@@ -29,6 +29,7 @@ import java.util
 import scala.collection.JavaConverters._
 
 class PaimonQueryTest extends PaimonSparkTestBase {
+  import testImplicits._
 
   fileFormats.foreach {
     fileFormat =>
@@ -66,13 +67,55 @@ class PaimonQueryTest extends PaimonSparkTestBase {
 
   }
 
+  test("Query metadata columns for bucket") {
+    withTable("T") {
+      spark.sql(
+        """
+          |CREATE TABLE T (c1 INT, c2 STRING) TBLPROPERTIES 
('bucket-key'='c1', 'bucket'='3')
+          |""".stripMargin)
+
+      spark.sql("""
+                  |INSERT INTO T
+                  |VALUES (1, 'x1'), (2, 'x3'), (3, 'x3'), (4, 'x4'), (5, 
'x5'), (6, 'x6')
+                  |""".stripMargin)
+
+      val res = spark.sql("""
+                            |SELECT __paimon_partition, __paimon_bucket FROM T
+                            |GROUP BY __paimon_partition, __paimon_bucket
+                            |ORDER BY __paimon_partition, __paimon_bucket
+                            |""".stripMargin)
+      checkAnswer(res, Row(Row(), 0) :: Row(Row(), 1) :: Row(Row(), 2) :: Nil)
+    }
+  }
+
+  test("Query metadata columns for partition") {
+    withTable("T") {
+      spark.sql("""
+                  |CREATE TABLE T (c1 INT) PARTITIONED BY(p1 INT, p2 String)
+                  |""".stripMargin)
+
+      spark.sql("""
+                  |INSERT INTO T
+                  |VALUES (1, 1, 'x1'), (2, 1, 'x2'), (3, 2, 'x1'), (4, 2, 
'x2'), (5, 3, 'x3')
+                  |""".stripMargin)
+
+      val res = spark.sql("""
+                            |SELECT __paimon_partition, __paimon_bucket FROM T
+                            |GROUP BY __paimon_partition, __paimon_bucket
+                            |ORDER BY __paimon_partition, __paimon_bucket
+                            |""".stripMargin)
+      checkAnswer(
+        res,
+        Row(Row(1, "x1"), 0) :: Row(Row(1, "x2"), 0) :: Row(Row(2, "x1"), 0) ::
+          Row(Row(2, "x2"), 0) :: Row(Row(3, "x3"), 0) :: Nil)
+    }
+  }
+
   fileFormats.foreach {
     fileFormat =>
       bucketModes.foreach {
         bucketMode =>
           test(s"Query input_file_name(): file.format=$fileFormat, 
bucket=$bucketMode") {
-            val _spark: SparkSession = spark
-            import _spark.implicits._
 
             withTable("T") {
               val bucketProp = if (bucketMode != -1) {

Reply via email to