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 2c87f4a6ea [spark] Support timestamp_ntz and variant in generic row
access (#9493)
2c87f4a6ea is described below
commit 2c87f4a6ea8afb530fda23b464a5fe1042ab42fe
Author: shyjsarah <[email protected]>
AuthorDate: Mon Aug 31 18:14:25 2026 +0800
[spark] Support timestamp_ntz and variant in generic row access (#9493)
---
.../apache/spark/sql/paimon/shims/Spark4Shim.scala | 3 ++
.../spark/data/SparkInternalRowVariantTest.scala | 41 ++++++++++++++++++++++
.../paimon/spark/AbstractSparkInternalRow.java | 6 +++-
.../apache/spark/sql/paimon/shims/SparkShim.scala | 2 ++
.../apache/paimon/spark/SparkInternalRowTest.java | 12 +++++++
.../apache/spark/sql/paimon/shims/Spark3Shim.scala | 3 ++
.../apache/spark/sql/paimon/shims/Spark4Shim.scala | 3 ++
7 files changed, 69 insertions(+), 1 deletion(-)
diff --git
a/paimon-spark/paimon-spark-4.0/src/main/scala/org/apache/spark/sql/paimon/shims/Spark4Shim.scala
b/paimon-spark/paimon-spark-4.0/src/main/scala/org/apache/spark/sql/paimon/shims/Spark4Shim.scala
index 8cdc6e453d..c348349fe9 100644
---
a/paimon-spark/paimon-spark-4.0/src/main/scala/org/apache/spark/sql/paimon/shims/Spark4Shim.scala
+++
b/paimon-spark/paimon-spark-4.0/src/main/scala/org/apache/spark/sql/paimon/shims/Spark4Shim.scala
@@ -375,6 +375,9 @@ class Spark4Shim extends SparkShim {
new GenericVariant(v.getValue, v.getMetadata)
}
+ override def toSparkVariant(variant: Variant): Object =
+ new VariantVal(variant.value(), variant.metadata())
+
override def isSparkVariantType(dataType:
org.apache.spark.sql.types.DataType): Boolean =
dataType.isInstanceOf[VariantType]
diff --git
a/paimon-spark/paimon-spark-4.0/src/test/scala/org/apache/paimon/spark/data/SparkInternalRowVariantTest.scala
b/paimon-spark/paimon-spark-4.0/src/test/scala/org/apache/paimon/spark/data/SparkInternalRowVariantTest.scala
new file mode 100644
index 0000000000..ee00a9b4c5
--- /dev/null
+++
b/paimon-spark/paimon-spark-4.0/src/test/scala/org/apache/paimon/spark/data/SparkInternalRowVariantTest.scala
@@ -0,0 +1,41 @@
+/*
+ * 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.data.GenericRow
+import org.apache.paimon.data.variant.GenericVariant
+import org.apache.paimon.types.{RowType, VariantType}
+
+import org.apache.spark.SparkFunSuite
+import org.apache.spark.sql.types.DataTypes
+import org.apache.spark.unsafe.types.VariantVal
+
+class SparkInternalRowVariantTest extends SparkFunSuite {
+
+ test("get variant with generic data type access") {
+ val variant = GenericVariant.fromJson("""{"id":1}""")
+ val rowType = RowType.of(new VariantType())
+ val row = SparkInternalRow.create(rowType).replace(GenericRow.of(variant))
+
+ val actual = row.get(0, DataTypes.VariantType).asInstanceOf[VariantVal]
+
+ assert(actual.getValue.sameElements(variant.value()))
+ assert(actual.getMetadata.sameElements(variant.metadata()))
+ }
+}
diff --git
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/AbstractSparkInternalRow.java
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/AbstractSparkInternalRow.java
index 46c48833d0..206fc0e20a 100644
---
a/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/AbstractSparkInternalRow.java
+++
b/paimon-spark/paimon-spark-common/src/main/java/org/apache/paimon/spark/AbstractSparkInternalRow.java
@@ -47,6 +47,7 @@ import org.apache.spark.sql.types.NullType;
import org.apache.spark.sql.types.ShortType;
import org.apache.spark.sql.types.StringType;
import org.apache.spark.sql.types.StructType;
+import org.apache.spark.sql.types.TimestampNTZType;
import org.apache.spark.sql.types.TimestampType;
import org.apache.spark.sql.types.UserDefinedType;
import org.apache.spark.sql.types.VarcharType;
@@ -236,7 +237,7 @@ public abstract class AbstractSparkInternalRow extends
SparkInternalRow {
if (dataType instanceof DateType) {
return getInt(ordinal);
}
- if (dataType instanceof TimestampType) {
+ if (dataType instanceof TimestampType || dataType instanceof
TimestampNTZType) {
return getLong(ordinal);
}
if (dataType instanceof CalendarIntervalType) {
@@ -257,6 +258,9 @@ public abstract class AbstractSparkInternalRow extends
SparkInternalRow {
if (dataType instanceof UserDefinedType) {
return get(ordinal, ((UserDefinedType<?>) dataType).sqlType());
}
+ if (SparkShimLoader.shim().isSparkVariantType(dataType)) {
+ return
SparkShimLoader.shim().toSparkVariant(row.getVariant(ordinal));
+ }
if (SparkShimLoader.shim().isSparkGeometryType(dataType)) {
org.apache.paimon.types.GeometryType geometryType =
(org.apache.paimon.types.GeometryType)
rowType.getTypeAt(ordinal);
diff --git
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/spark/sql/paimon/shims/SparkShim.scala
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/spark/sql/paimon/shims/SparkShim.scala
index 8d0cd71579..e4a0a315f2 100644
---
a/paimon-spark/paimon-spark-common/src/main/scala/org/apache/spark/sql/paimon/shims/SparkShim.scala
+++
b/paimon-spark/paimon-spark-common/src/main/scala/org/apache/spark/sql/paimon/shims/SparkShim.scala
@@ -265,6 +265,8 @@ trait SparkShim {
def toPaimonVariant(array: ArrayData, pos: Int): Variant
+ def toSparkVariant(variant: Variant): Object
+
def isSparkVariantType(dataType: org.apache.spark.sql.types.DataType):
Boolean
def SparkVariantType(): org.apache.spark.sql.types.DataType
diff --git
a/paimon-spark/paimon-spark-ut/src/test/java/org/apache/paimon/spark/SparkInternalRowTest.java
b/paimon-spark/paimon-spark-ut/src/test/java/org/apache/paimon/spark/SparkInternalRowTest.java
index cf2886e947..e56a07a527 100644
---
a/paimon-spark/paimon-spark-ut/src/test/java/org/apache/paimon/spark/SparkInternalRowTest.java
+++
b/paimon-spark/paimon-spark-ut/src/test/java/org/apache/paimon/spark/SparkInternalRowTest.java
@@ -30,6 +30,7 @@ import org.apache.paimon.data.Timestamp;
import org.apache.paimon.fs.local.LocalFileIO;
import org.apache.paimon.spark.data.SparkInternalRow;
import org.apache.paimon.types.DataTypes;
+import org.apache.paimon.types.RowType;
import org.apache.paimon.utils.DateTimeUtils;
import org.apache.paimon.utils.UriReaderFactory;
@@ -178,6 +179,17 @@ public class SparkInternalRowTest {
.hasMessage("Spark MAP<X, BLOB> does not support null keys.");
}
+ @Test
+ public void testGetTimestampNtz() {
+ Timestamp timestamp =
+
Timestamp.fromLocalDateTime(LocalDateTime.parse("2026-08-31T10:15:30.123456"));
+ RowType rowType = RowType.of(DataTypes.TIMESTAMP());
+ SparkInternalRow row =
SparkInternalRow.create(rowType).replace(GenericRow.of(timestamp));
+
+ assertThat(row.get(0,
SparkTypeUtils.fromPaimonType(DataTypes.TIMESTAMP())))
+ .isEqualTo(timestamp.toMicros());
+ }
+
private String sparkRowToString(org.apache.spark.sql.Row row) {
return JavaConverters.seqAsJavaList(row.toSeq()).stream()
.map(
diff --git
a/paimon-spark/paimon-spark3-common/src/main/scala/org/apache/spark/sql/paimon/shims/Spark3Shim.scala
b/paimon-spark/paimon-spark3-common/src/main/scala/org/apache/spark/sql/paimon/shims/Spark3Shim.scala
index 3ea437da35..9b9b617416 100644
---
a/paimon-spark/paimon-spark3-common/src/main/scala/org/apache/spark/sql/paimon/shims/Spark3Shim.scala
+++
b/paimon-spark/paimon-spark3-common/src/main/scala/org/apache/spark/sql/paimon/shims/Spark3Shim.scala
@@ -349,6 +349,9 @@ class Spark3Shim extends SparkShim {
override def toPaimonVariant(o: Object): Variant = throw new
UnsupportedOperationException()
+ override def toSparkVariant(variant: Variant): Object =
+ throw new UnsupportedOperationException("Variant requires Spark 4.0 or
later")
+
override def isSparkVariantType(dataType:
org.apache.spark.sql.types.DataType): Boolean = false
override def SparkVariantType(): org.apache.spark.sql.types.DataType =
diff --git
a/paimon-spark/paimon-spark4-common/src/main/scala/org/apache/spark/sql/paimon/shims/Spark4Shim.scala
b/paimon-spark/paimon-spark4-common/src/main/scala/org/apache/spark/sql/paimon/shims/Spark4Shim.scala
index 4ae3bb1892..ae78e6eb79 100644
---
a/paimon-spark/paimon-spark4-common/src/main/scala/org/apache/spark/sql/paimon/shims/Spark4Shim.scala
+++
b/paimon-spark/paimon-spark4-common/src/main/scala/org/apache/spark/sql/paimon/shims/Spark4Shim.scala
@@ -353,6 +353,9 @@ class Spark4Shim extends SparkShim {
new GenericVariant(v.getValue, v.getMetadata)
}
+ override def toSparkVariant(variant: Variant): Object =
+ new VariantVal(variant.value(), variant.metadata())
+
override def isSparkVariantType(dataType:
org.apache.spark.sql.types.DataType): Boolean =
dataType.isInstanceOf[VariantType]