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 54ae6fb8f [spark] supports metadata columns (#3307)
54ae6fb8f is described below

commit 54ae6fb8f12e5b13b2039b012b2e11b9e8e039ba
Author: Yann Byron <[email protected]>
AuthorDate: Sat May 11 13:00:16 2024 +0800

    [spark] supports metadata columns (#3307)
---
 .../java/org/apache/paimon/spark/SparkTable.java   | 11 +++++
 .../org/apache/paimon/spark/PaimonBaseScan.scala   | 21 +++++++--
 .../org/apache/paimon/spark/PaimonBatch.scala      | 12 +++--
 .../paimon/spark/PaimonPartitionReader.scala       |  6 ++-
 .../spark/PaimonPartitionReaderFactory.scala       | 21 +++++++--
 .../paimon/spark/PaimonRecordReaderIterator.scala  | 44 +++++++++++++++--
 .../org/apache/paimon/spark/PaimonStatistics.scala | 20 ++++----
 .../paimon/spark/schema/PaimonMetadataColumn.scala | 55 ++++++++++++++++++++++
 .../spark/sources/PaimonMicroBatchStream.scala     |  2 +-
 .../apache/paimon/spark/sql/PaimonQueryTest.scala  | 36 ++++++++++++++
 10 files changed, 201 insertions(+), 27 deletions(-)

diff --git 
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/SparkTable.java
 
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/SparkTable.java
index c319679de..be9f4309a 100644
--- 
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/SparkTable.java
+++ 
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/SparkTable.java
@@ -20,10 +20,13 @@ package org.apache.paimon.spark;
 
 import org.apache.paimon.CoreOptions;
 import org.apache.paimon.options.Options;
+import org.apache.paimon.spark.schema.PaimonMetadataColumn$;
 import org.apache.paimon.table.DataTable;
 import org.apache.paimon.table.FileStoreTable;
 import org.apache.paimon.table.Table;
 
+import org.apache.spark.sql.connector.catalog.MetadataColumn;
+import org.apache.spark.sql.connector.catalog.SupportsMetadataColumns;
 import org.apache.spark.sql.connector.catalog.SupportsRead;
 import org.apache.spark.sql.connector.catalog.SupportsWrite;
 import org.apache.spark.sql.connector.catalog.TableCapability;
@@ -49,6 +52,7 @@ public class SparkTable
         implements org.apache.spark.sql.connector.catalog.Table,
                 SupportsRead,
                 SupportsWrite,
+                SupportsMetadataColumns,
                 PaimonPartitionManagement {
 
     private final Table table;
@@ -135,4 +139,11 @@ public class SparkTable
         SparkTable that = (SparkTable) o;
         return Objects.equals(table, that.table);
     }
+
+    @Override
+    public MetadataColumn[] metadataColumns() {
+        return new MetadataColumn[] {
+            PaimonMetadataColumn$.MODULE$.FILE_PATH(), 
PaimonMetadataColumn$.MODULE$.ROW_INDEX()
+        };
+    }
 }
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 7a49167a9..f6df901a4 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
@@ -20,6 +20,7 @@ package org.apache.paimon.spark
 
 import org.apache.paimon.{stats, CoreOptions}
 import org.apache.paimon.predicate.{Predicate, PredicateBuilder}
+import org.apache.paimon.spark.schema.PaimonMetadataColumn
 import org.apache.paimon.spark.sources.PaimonMicroBatchStream
 import org.apache.paimon.spark.statistics.StatisticsHelper
 import org.apache.paimon.table.{DataTable, FileStoreTable, Table}
@@ -51,6 +52,16 @@ abstract class PaimonBaseScan(
 
   private lazy val tableSchema = SparkTypeUtils.fromPaimonRowType(tableRowType)
 
+  private val (tableFields, metadataFields) = {
+    val requiredFieldNames = requiredSchema.fieldNames
+    val _tableFields = tableSchema.filter(field => 
requiredFieldNames.contains(field.name))
+    val _metadataFields =
+      requiredSchema
+        .filterNot(field => tableSchema.fieldNames.contains(field.name))
+        .filter(field => 
PaimonMetadataColumn.SUPPORTED_METADATA_COLUMNS.contains(field.name))
+    (_tableFields, _metadataFields)
+  }
+
   protected var runtimeFilters: Array[Filter] = Array.empty
 
   protected var splits: Array[Split] = _
@@ -60,14 +71,14 @@ abstract class PaimonBaseScan(
   lazy val statistics: Optional[stats.Statistics] = table.statistics()
 
   lazy val requiredStatsSchema: StructType = {
-    val fieldNames = requiredSchema.fieldNames ++ 
reservedFilters.flatMap(_.references)
+    val fieldNames = tableFields.map(_.name) ++ 
reservedFilters.flatMap(_.references)
     StructType(tableSchema.filter(field => fieldNames.contains(field.name)))
   }
 
   lazy val readBuilder: ReadBuilder = {
     val _readBuilder = table.newReadBuilder()
 
-    val projection = readSchema().fieldNames.map(field => 
tableRowType.getFieldNames.indexOf(field))
+    val projection = tableFields.map(field => 
tableSchema.fieldNames.indexOf(field.name)).toArray
     _readBuilder.withProjection(projection)
     if (filters.nonEmpty) {
       val pushedPredicate = PredicateBuilder.and(filters: _*)
@@ -90,12 +101,12 @@ abstract class PaimonBaseScan(
   }
 
   override def readSchema(): StructType = {
-    val requiredFieldNames = requiredSchema.fieldNames
-    StructType(tableSchema.filter(field => 
requiredFieldNames.contains(field.name)))
+    StructType(tableFields ++ metadataFields)
   }
 
   override def toBatch: Batch = {
-    PaimonBatch(getSplits, readBuilder)
+    val metadataColumns = metadataFields.map(field => 
PaimonMetadataColumn.get(field.name))
+    PaimonBatch(getSplits, readBuilder, metadataColumns)
   }
 
   override def toMicroBatchStream(checkpointLocation: String): 
MicroBatchStream = {
diff --git 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonBatch.scala
 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonBatch.scala
index 7544aa2d9..c7b782573 100644
--- 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonBatch.scala
+++ 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonBatch.scala
@@ -18,20 +18,26 @@
 
 package org.apache.paimon.spark
 
+import org.apache.paimon.spark.schema.PaimonMetadataColumn
 import org.apache.paimon.table.source.{ReadBuilder, Split}
 
 import org.apache.spark.sql.connector.read.{Batch, InputPartition, 
PartitionReaderFactory}
+import org.apache.spark.sql.types.StructType
 
 import java.util.Objects
 
 /** A Spark [[Batch]] for paimon. */
-case class PaimonBatch(splits: Array[Split], readBuilder: ReadBuilder) extends 
Batch {
+case class PaimonBatch(
+    splits: Array[Split],
+    readBuilder: ReadBuilder,
+    metadataColumns: Seq[PaimonMetadataColumn] = Seq.empty)
+  extends Batch {
 
   override def planInputPartitions(): Array[InputPartition] =
     splits.map(new SparkInputPartition(_).asInstanceOf[InputPartition])
 
-  override def createReaderFactory(): PartitionReaderFactory = new 
PaimonPartitionReaderFactory(
-    readBuilder)
+  override def createReaderFactory(): PartitionReaderFactory =
+    PaimonPartitionReaderFactory(readBuilder, metadataColumns)
 
   override def equals(obj: Any): Boolean = {
     obj match {
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 c4e694814..1509ea83c 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
@@ -20,6 +20,7 @@ package org.apache.paimon.spark
 
 import org.apache.paimon.data.{InternalRow => PaimonInternalRow}
 import org.apache.paimon.reader.{RecordReader, RecordReaderIterator}
+import org.apache.paimon.spark.schema.PaimonMetadataColumn
 import org.apache.paimon.table.source.{DataSplit, Split}
 
 import org.apache.spark.sql.catalyst.InternalRow
@@ -33,14 +34,15 @@ import scala.collection.JavaConverters._
 case class PaimonPartitionReader(
     readFunc: Split => RecordReader[PaimonInternalRow],
     partition: SparkInputPartition,
-    row: SparkInternalRow
+    row: SparkInternalRow,
+    metadataColumns: Seq[PaimonMetadataColumn]
 ) extends PartitionReader[InternalRow] {
 
   private lazy val split: Split = partition.split
 
   private lazy val iterator = {
     val reader = readFunc(split)
-    PaimonRecordReaderIterator(reader)
+    PaimonRecordReaderIterator(reader, metadataColumns)
   }
 
   override def next(): Boolean = {
diff --git 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonPartitionReaderFactory.scala
 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonPartitionReaderFactory.scala
index 961aad8a2..3ac724fcc 100644
--- 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonPartitionReaderFactory.scala
+++ 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonPartitionReaderFactory.scala
@@ -22,25 +22,38 @@ import org.apache.paimon.data
 import org.apache.paimon.disk.IOManager
 import org.apache.paimon.reader.RecordReader
 import org.apache.paimon.spark.SparkUtils.createIOManager
+import org.apache.paimon.spark.schema.PaimonMetadataColumn
 import org.apache.paimon.table.source.{ReadBuilder, Split}
+import org.apache.paimon.types.RowType
 
 import org.apache.spark.sql.catalyst.InternalRow
 import org.apache.spark.sql.connector.read.{InputPartition, PartitionReader, 
PartitionReaderFactory}
 
+import java.util.{ArrayList => JList}
 import java.util.Objects
 
-case class PaimonPartitionReaderFactory(readBuilder: ReadBuilder) extends 
PartitionReaderFactory {
+import scala.collection.JavaConverters._
+
+case class PaimonPartitionReaderFactory(
+    readBuilder: ReadBuilder,
+    metadataColumns: Seq[PaimonMetadataColumn] = Seq.empty)
+  extends PartitionReaderFactory {
 
   private lazy val ioManager: IOManager = createIOManager()
 
-  private lazy val row: SparkInternalRow = new 
SparkInternalRow(readBuilder.readType())
+  private lazy val row: SparkInternalRow = {
+    val dataFields = new JList(readBuilder.readType().getFields)
+    dataFields.addAll(metadataColumns.map(_.toPaimonDataField).asJava)
+    val rowType = new RowType(dataFields)
+    new SparkInternalRow(rowType)
+  }
 
   override def createReader(partition: InputPartition): 
PartitionReader[InternalRow] = {
     partition match {
       case paimonInputPartition: SparkInputPartition =>
         val readFunc: Split => RecordReader[data.InternalRow] =
           (split: Split) => 
readBuilder.newRead().withIOManager(ioManager).createReader(split)
-        PaimonPartitionReader(readFunc, paimonInputPartition, row)
+        PaimonPartitionReader(readFunc, paimonInputPartition, row, 
metadataColumns)
       case _ =>
         throw new RuntimeException(s"It's not a Paimon input partition, 
$partition")
     }
@@ -49,7 +62,7 @@ case class PaimonPartitionReaderFactory(readBuilder: 
ReadBuilder) extends Partit
   override def equals(obj: Any): Boolean = {
     obj match {
       case other: PaimonPartitionReaderFactory =>
-        this.readBuilder.equals(other.readBuilder)
+        this.readBuilder.equals(other.readBuilder) && this.metadataColumns == 
other.metadataColumns
 
       case _ => false
     }
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 36fb03fd6..ce880e670 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
@@ -18,23 +18,32 @@
 
 package org.apache.paimon.spark
 
-import org.apache.paimon.data.{InternalRow => PaimonInternalRow}
+import org.apache.paimon.data.{BinaryString, GenericRow, InternalRow => 
PaimonInternalRow, JoinedRow}
 import org.apache.paimon.fs.Path
 import org.apache.paimon.reader.{FileRecordIterator, RecordReader}
+import org.apache.paimon.spark.schema.PaimonMetadataColumn
 import org.apache.paimon.utils.CloseableIterator
 
 import org.apache.spark.sql.PaimonUtils
 
 import java.io.IOException
 
-case class PaimonRecordReaderIterator(reader: RecordReader[PaimonInternalRow])
+case class PaimonRecordReaderIterator(
+    reader: RecordReader[PaimonInternalRow],
+    metadataColumns: Seq[PaimonMetadataColumn])
   extends CloseableIterator[PaimonInternalRow] {
 
   private var lastFilePath: Path = _
+  private var isFileRecordIterator: Boolean = false
   private var currentIterator: RecordReader.RecordIterator[PaimonInternalRow] 
= readBatch()
   private var advanced = false
   private var currentResult: PaimonInternalRow = _
 
+  private val needMetadata = metadataColumns.nonEmpty
+  private val metadataRow: GenericRow =
+    GenericRow.of(Array.fill(metadataColumns.size)(null.asInstanceOf[AnyRef]): 
_*)
+  private val joinedRow: JoinedRow = JoinedRow.join(null, metadataRow)
+
   override def hasNext: Boolean = {
     if (currentIterator == null) {
       false
@@ -69,11 +78,13 @@ case class PaimonRecordReaderIterator(reader: 
RecordReader[PaimonInternalRow])
     val iter = reader.readBatch()
     iter match {
       case fileRecordIterator: FileRecordIterator[_] =>
+        isFileRecordIterator = true
         if (lastFilePath != fileRecordIterator.filePath()) {
           
PaimonUtils.setInputFileName(fileRecordIterator.filePath().toUri.toString)
           lastFilePath = fileRecordIterator.filePath()
         }
       case _ =>
+        isFileRecordIterator = false
     }
     iter
   }
@@ -84,7 +95,22 @@ case class PaimonRecordReaderIterator(reader: 
RecordReader[PaimonInternalRow])
       try {
         var stop = false
         while (!stop) {
-          currentResult = currentIterator.next
+          val dataRow = currentIterator.next()
+          if (dataRow != null) {
+            if (needMetadata) {
+              if (!isFileRecordIterator) {
+                throw new RuntimeException(
+                  "There need be FileRecoredIterator when metadata columns are 
required.")
+              }
+              
updateMetadataRow(currentIterator.asInstanceOf[FileRecordIterator[PaimonInternalRow]])
+              currentResult = joinedRow.replace(dataRow, metadataRow)
+            } else {
+              currentResult = dataRow
+            }
+          } else {
+            currentResult = null
+          }
+
           if (currentResult != null) {
             stop = true
           } else {
@@ -102,4 +128,16 @@ case class PaimonRecordReaderIterator(reader: 
RecordReader[PaimonInternalRow])
       }
     }
   }
+
+  private def updateMetadataRow(fileRecordIterator: 
FileRecordIterator[PaimonInternalRow]): Unit = {
+    metadataColumns.zipWithIndex.foreach {
+      case (metadataColumn, index) =>
+        metadataColumn.name match {
+          case PaimonMetadataColumn.ROW_INDEX.name =>
+            metadataRow.setField(index, fileRecordIterator.returnedPosition())
+          case PaimonMetadataColumn.FILE_PATH.name =>
+            metadataRow.setField(index, 
BinaryString.fromString(lastFilePath.toUri.toString))
+        }
+    }
+  }
 }
diff --git 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonStatistics.scala
 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonStatistics.scala
index abaac6382..fcf386397 100644
--- 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonStatistics.scala
+++ 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/PaimonStatistics.scala
@@ -48,19 +48,21 @@ case class PaimonStatistics[T <: PaimonBaseScan](scan: T) 
extends Statistics {
     if (paimonStats.isPresent) paimonStats.get().mergedRecordCount() else 
OptionalLong.of(rowCount)
 
   override def columnStats(): java.util.Map[NamedReference, ColumnStatistics] 
= {
-    val requiredFields = scan.requiredStatsSchema.fieldNames.toList.asJava
+    val requiredFields = scan.requiredStatsSchema.fieldNames
     val resultMap = new java.util.HashMap[NamedReference, ColumnStatistics]()
     if (paimonStats.isPresent) {
       val paimonColStats = paimonStats.get().colStats()
-      scan.tableRowType.getFields
-        .stream()
-        .filter(
-          field => requiredFields.contains(field.name) && 
paimonColStats.containsKey(field.name()))
-        .forEach(
-          f =>
+      scan.tableRowType.getFields.asScala
+        .filter {
+          field => requiredFields.contains(field.name) && 
paimonColStats.containsKey(field.name())
+        }
+        .foreach {
+          field =>
             resultMap.put(
-              PaimonUtils.fieldReference(f.name()),
-              PaimonColumnStats(f.`type`(), paimonColStats.get(f.name()))))
+              PaimonUtils.fieldReference(field.name()),
+              PaimonColumnStats(field.`type`(), 
paimonColStats.get(field.name()))
+            )
+        }
     }
     resultMap
   }
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
new file mode 100644
index 000000000..74ce7090d
--- /dev/null
+++ 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/schema/PaimonMetadataColumn.scala
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.paimon.spark.schema
+
+import org.apache.paimon.spark.SparkTypeUtils
+import org.apache.paimon.types.DataField
+
+import org.apache.spark.sql.connector.catalog.MetadataColumn
+import org.apache.spark.sql.types.{DataType, LongType, StringType}
+
+case class PaimonMetadataColumn(id: Int, override val name: String, override 
val dataType: DataType)
+  extends MetadataColumn {
+
+  def toPaimonDataField: DataField = {
+    new DataField(id, name, SparkTypeUtils.toPaimonType(dataType));
+  }
+}
+
+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 ROW_INDEX: PaimonMetadataColumn =
+    PaimonMetadataColumn(Int.MaxValue - 100, ROW_INDEX_COLUMN, LongType)
+  val FILE_PATH: PaimonMetadataColumn =
+    PaimonMetadataColumn(Int.MaxValue - 101, FILE_PATH_COLUMN, StringType)
+
+  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.")
+    }
+  }
+}
diff --git 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/sources/PaimonMicroBatchStream.scala
 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/sources/PaimonMicroBatchStream.scala
index 355902f0a..78e2e8132 100644
--- 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/sources/PaimonMicroBatchStream.scala
+++ 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/sources/PaimonMicroBatchStream.scala
@@ -127,7 +127,7 @@ class PaimonMicroBatchStream(
   }
 
   override def createReaderFactory(): PartitionReaderFactory = {
-    new PaimonPartitionReaderFactory(readBuilder)
+    PaimonPartitionReaderFactory(readBuilder)
   }
 
   override def initialOffset(): Offset = {
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 ef683366d..2990f8cee 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
@@ -30,6 +30,42 @@ import scala.collection.JavaConverters._
 
 class PaimonQueryTest extends PaimonSparkTestBase {
 
+  fileFormats.foreach {
+    fileFormat =>
+      bucketModes.foreach {
+        bucketMode =>
+          test(s"Query metadata columns: file.format=$fileFormat, 
bucket=$bucketMode") {
+            withTable("T") {
+
+              spark.sql(
+                s"""
+                   |CREATE TABLE T (id INT, name STRING)
+                   |TBLPROPERTIES ('primary-key' = 'id', 
'file.format'='$fileFormat', 'bucket'='$bucketMode')
+                   |""".stripMargin)
+
+              spark.sql("""
+                          |INSERT INTO T
+                          |VALUES (1, 'x1'), (2, 'x3'), (3, 'x3'), (4, 'x4'), 
(5, 'x5')
+                          |""".stripMargin)
+
+              val location = loadTable("T").location().toUri.toString
+              val res = spark.sql(
+                s"""
+                   |SELECT SUM(cnt)
+                   |FROM (
+                   |  SELECT __paimon_file_path AS path, count(1) AS cnt, 
count(distinct __paimon_row_index) AS dc
+                   |  FROM T
+                   |  GROUP BY __paimon_file_path
+                   |)
+                   |WHERE startswith(path, '$location') and endswith(path, 
'.$fileFormat') and cnt == dc
+                   |""".stripMargin)
+              checkAnswer(res, Row(5))
+            }
+          }
+      }
+
+  }
+
   fileFormats.foreach {
     fileFormat =>
       bucketModes.foreach {

Reply via email to