This is an automated email from the ASF dual-hosted git repository.
JingsongLi 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 4220791657 [spark] Read split statistics through the query
authorization wrapper (#10051)
4220791657 is described below
commit 422079165759af3494a9ec49a68a27915b75eb35
Author: Jiajia Li <[email protected]>
AuthorDate: Tue Sep 22 10:49:26 2026 +0800
[spark] Read split statistics through the query authorization wrapper
(#10051)
---
.../org/apache/paimon/spark/util/SplitUtils.scala | 11 +--
.../spark/util/SplitUtilsQueryAuthTest.scala | 83 ++++++++++++++++++++++
2 files changed, 90 insertions(+), 4 deletions(-)
diff --git
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/util/SplitUtils.scala
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/util/SplitUtils.scala
index c485fe4da3..c970db1297 100644
---
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/util/SplitUtils.scala
+++
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/util/SplitUtils.scala
@@ -19,7 +19,7 @@
package org.apache.paimon.spark.util
import org.apache.paimon.table.format.FormatDataSplit
-import org.apache.paimon.table.source.{DataSplit, Split}
+import org.apache.paimon.table.source.{DataSplit, Split, Splits}
import java.util.{Collections => JCollections}
@@ -27,8 +27,11 @@ import scala.collection.JavaConverters._
object SplitUtils {
+ /** Only metadata is read here, so peeling the authorization wrapper exposes
no rows. */
+ private def underlying(split: Split): Split = Splits.underlying(split)
+
def splitSize(split: Split): Long = {
- split match {
+ underlying(split) match {
case ds: DataSplit =>
ds.dataFiles().asScala.map(_.fileSize).sum
case fs: FormatDataSplit =>
@@ -40,7 +43,7 @@ object SplitUtils {
def fileCount(split: Split): Long = dataFileCount(split) +
deleteFileCount(split)
def dataFileCount(split: Split): Long = {
- split match {
+ underlying(split) match {
case ds: DataSplit => ds.dataFiles().size()
case fs: FormatDataSplit => fs.fileCount()
case _ => 0
@@ -48,7 +51,7 @@ object SplitUtils {
}
def deleteFileCount(split: Split): Long = {
- split match {
+ underlying(split) match {
case ds: DataSplit =>
ds.deletionFiles()
.orElse(JCollections.emptyList())
diff --git
a/paimon-spark/paimon-spark-common/src/test/scala/org/apache/paimon/spark/util/SplitUtilsQueryAuthTest.scala
b/paimon-spark/paimon-spark-common/src/test/scala/org/apache/paimon/spark/util/SplitUtilsQueryAuthTest.scala
new file mode 100644
index 0000000000..cada2b24a2
--- /dev/null
+++
b/paimon-spark/paimon-spark-common/src/test/scala/org/apache/paimon/spark/util/SplitUtilsQueryAuthTest.scala
@@ -0,0 +1,83 @@
+/*
+ * 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.util
+
+import org.apache.paimon.catalog.TableQueryAuthResult
+import org.apache.paimon.data.BinaryRow
+import org.apache.paimon.io.DataFileMeta
+import org.apache.paimon.spark.PaimonRecordReaderIterator
+import org.apache.paimon.spark.schema.PaimonMetadataColumn
+import org.apache.paimon.stats.SimpleStats
+import org.apache.paimon.table.source.{DataSplit, QueryAuthSplit, Split}
+
+import org.apache.spark.sql.types.BinaryType
+import org.scalatest.funsuite.AnyFunSuite
+
+import java.util.Collections
+
+class SplitUtilsQueryAuthTest extends AnyFunSuite {
+
+ private def dataSplit(): DataSplit = {
+ val file = DataFileMeta.forAppend(
+ "data-0.parquet",
+ 1024L,
+ 10L,
+ SimpleStats.EMPTY_STATS,
+ 0L,
+ 9L,
+ 0L,
+ Collections.emptyList[String](),
+ null,
+ null,
+ null,
+ null,
+ null,
+ null)
+ DataSplit
+ .builder()
+ .withSnapshot(1L)
+ .withPartition(BinaryRow.EMPTY_ROW)
+ .withBucket(0)
+ .withBucketPath("bucket-0")
+ .withDataFiles(Collections.singletonList(file))
+ .build()
+ }
+
+ private def authorized(split: Split): Split =
+ new QueryAuthSplit(split, new TableQueryAuthResult(null, null))
+
+ test("statistics survive the query-authorization wrapper") {
+ val split = dataSplit()
+ assert(SplitUtils.splitSize(split) == 1024L)
+ assert(SplitUtils.dataFileCount(split) == 1L)
+
+ val wrapped = authorized(split)
+ assert(SplitUtils.splitSize(wrapped) == 1024L, "split size collapsed under
query auth")
+ assert(SplitUtils.dataFileCount(wrapped) == 1L, "file count collapsed
under query auth")
+ }
+
+ test("partition metadata columns are refused on a query-authorized split") {
+ val partitionColumn =
+ PaimonMetadataColumn(-1, PaimonMetadataColumn.PARTITION_COLUMN,
BinaryType)
+ val error = intercept[RuntimeException] {
+ PaimonRecordReaderIterator(null, Seq(partitionColumn),
authorized(dataSplit()))
+ }
+ assert(error.getMessage.contains("DataSplit"))
+ }
+}