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 3d49b5044d [spark] Cache blob field lookup in SparkInternalRow (#8943)
3d49b5044d is described below

commit 3d49b5044d31c07107e6a9cc2405464e08a6e2f3
Author: Xiangyi Zhu <[email protected]>
AuthorDate: Thu Aug 6 17:44:01 2026 +0800

    [spark] Cache blob field lookup in SparkInternalRow (#8943)
---
 .../main/java/org/apache/paimon/types/RowType.java | 18 +++++
 .../paimon/spark/data/SparkInternalRow.scala       | 27 +++----
 .../paimon/spark/data/SparkInternalRowTest.scala   | 83 ++++++++++++++++++++++
 3 files changed, 110 insertions(+), 18 deletions(-)

diff --git a/paimon-api/src/main/java/org/apache/paimon/types/RowType.java 
b/paimon-api/src/main/java/org/apache/paimon/types/RowType.java
index 30b357f5b9..cadb88f317 100644
--- a/paimon-api/src/main/java/org/apache/paimon/types/RowType.java
+++ b/paimon-api/src/main/java/org/apache/paimon/types/RowType.java
@@ -70,6 +70,8 @@ public final class RowType extends DataType {
     private transient volatile Map<Integer, DataField> laziedFieldIdToField;
     private transient volatile Map<Integer, Integer> laziedFieldIdToIndex;
 
+    private transient volatile Set<Integer> laziedBlobFieldIndices;
+
     public RowType(boolean isNullable, List<DataField> fields) {
         super(isNullable, DataTypeRoot.ROW);
         this.fields =
@@ -121,6 +123,22 @@ public final class RowType extends DataType {
         return projection;
     }
 
+    /** Returns the indices of top-level BLOB fields. */
+    public Set<Integer> getBlobFieldIndices() {
+        Set<Integer> blobFieldIndices = this.laziedBlobFieldIndices;
+        if (blobFieldIndices == null) {
+            Set<Integer> indices = new HashSet<>();
+            for (int i = 0; i < fields.size(); i++) {
+                if (fields.get(i).type().getTypeRoot() == DataTypeRoot.BLOB) {
+                    indices.add(i);
+                }
+            }
+            blobFieldIndices = Collections.unmodifiableSet(indices);
+            this.laziedBlobFieldIndices = blobFieldIndices;
+        }
+        return blobFieldIndices;
+    }
+
     public boolean containsField(String fieldName) {
         return nameToField().containsKey(fieldName);
     }
diff --git 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/data/SparkInternalRow.scala
 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/data/SparkInternalRow.scala
index 71d1361d0c..e0a5f5ac04 100644
--- 
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/data/SparkInternalRow.scala
+++ 
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/data/SparkInternalRow.scala
@@ -18,12 +18,12 @@
 
 package org.apache.paimon.spark.data
 
-import org.apache.paimon.types.{DataTypeRoot, RowType}
+import org.apache.paimon.types.RowType
 
 import org.apache.spark.sql.catalyst.InternalRow
 import org.apache.spark.sql.paimon.shims.SparkShimLoader
 
-import scala.collection.mutable
+import scala.collection.JavaConverters._
 
 abstract class SparkInternalRow extends InternalRow {
   def replace(row: org.apache.paimon.data.InternalRow): SparkInternalRow
@@ -38,24 +38,15 @@ object SparkInternalRow {
   }
 
   def create(rowType: RowType, blobAsDescriptor: Boolean): SparkInternalRow = {
-    val blobs = blobFields(rowType)
-    if (blobs.nonEmpty) {
-      SparkShimLoader.shim.createSparkInternalRowWithBlob(rowType, blobs, 
blobAsDescriptor)
-    } else {
+    val blobFieldIndices = rowType.getBlobFieldIndices
+    if (blobFieldIndices.isEmpty) {
       
SparkShimLoader.shim.createSparkInternalRow(rowType).withBlobAsDescriptor(blobAsDescriptor)
+    } else {
+      SparkShimLoader.shim.createSparkInternalRowWithBlob(
+        rowType,
+        blobFieldIndices.asScala.map(_.intValue()).toSet,
+        blobAsDescriptor)
     }
   }
 
-  private def blobFields(rowType: RowType): Set[Int] = {
-    var i: Int = 0
-    val blobFields = new mutable.HashSet[Int]()
-    while (i < rowType.getFieldCount) {
-      if (rowType.getTypeAt(i).getTypeRoot.equals(DataTypeRoot.BLOB)) {
-        blobFields.add(i)
-      }
-      i += 1
-    }
-    blobFields.toSet
-  }
-
 }
diff --git 
a/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/data/SparkInternalRowTest.scala
 
b/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/data/SparkInternalRowTest.scala
new file mode 100644
index 0000000000..b6bd9a77d6
--- /dev/null
+++ 
b/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/data/SparkInternalRowTest.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.data
+
+import org.apache.paimon.types.{BinaryType, DataField, DataTypeRoot, RowType}
+
+import org.apache.spark.SparkFunSuite
+
+import java.util.concurrent.atomic.AtomicInteger
+
+import scala.collection.JavaConverters._
+
+class SparkInternalRowTest extends SparkFunSuite {
+
+  test("cache blob field lookup for reused row type") {
+    val getTypeRootCount = new AtomicInteger()
+    val rowType = newRowType(getTypeRootCount)
+
+    SparkInternalRow.create(rowType)
+    SparkInternalRow.create(rowType)
+
+    assert(getTypeRootCount.get() == 10)
+    assert(rowType.getBlobFieldIndices.asScala == Set(3, 7))
+  }
+
+  test("cache blob field lookup per row type instance") {
+    val getTypeRootCount = new AtomicInteger()
+    val first = newRowType(getTypeRootCount)
+    val second = newRowType(getTypeRootCount)
+
+    assert(first == second)
+    SparkInternalRow.create(first)
+    SparkInternalRow.create(second)
+
+    assert(getTypeRootCount.get() == 20)
+  }
+
+  test("blob field indices are immutable") {
+    val rowType = newRowType(new AtomicInteger())
+
+    intercept[UnsupportedOperationException] {
+      rowType.getBlobFieldIndices.add(1)
+    }
+  }
+
+  private def newRowType(getTypeRootCount: AtomicInteger): RowType = {
+    new RowType((0 until 10).map {
+      i =>
+        val dataType =
+          if (i == 3 || i == 7) {
+            new CountingBinaryType(getTypeRootCount, DataTypeRoot.BLOB)
+          } else {
+            new CountingBinaryType(getTypeRootCount, DataTypeRoot.BINARY)
+          }
+        new DataField(i, s"f$i", dataType)
+    }.asJava)
+  }
+
+  private class CountingBinaryType(getTypeRootCount: AtomicInteger, typeRoot: 
DataTypeRoot)
+    extends BinaryType {
+
+    override def getTypeRoot: DataTypeRoot = {
+      getTypeRootCount.incrementAndGet()
+      typeRoot
+    }
+  }
+}

Reply via email to