You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spark.apache.org by ma...@apache.org on 2026/06/03 08:10:24 UTC

(spark) branch branch-4.x updated: [SPARK-57166][SQL] Reject nanosecond-capable timestamp types in built-in datasources and JDBC

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

MaxGekk pushed a commit to branch branch-4.x
in repository https://gitbox.apache.org/repos/asf/spark.git


The following commit(s) were added to refs/heads/branch-4.x by this push:
     new 50aaa9291bba [SPARK-57166][SQL] Reject nanosecond-capable timestamp types in built-in datasources and JDBC
50aaa9291bba is described below

commit 50aaa9291bba511164e8a8562570139e7e226189
Author: Maxim Gekk <ma...@gmail.com>
AuthorDate: Wed Jun 3 10:09:55 2026 +0200

    [SPARK-57166][SQL] Reject nanosecond-capable timestamp types in built-in datasources and JDBC
    
    ### What changes were proposed in this pull request?
    
    This PR makes all built-in file datasources and JDBC explicitly **reject** the nanosecond-capable timestamp types `TimestampNTZNanosType` and `TimestampLTZNanosType` on both the read and write paths, raising the existing `UNSUPPORTED_DATA_TYPE_FOR_DATASOURCE` error.
    
    An explicit arm is added before the `case _: AtomicType => true` catch-all in each datasource's `supportDataType` / `supportsDataType`:
    
    ```scala
    // Nanosecond-capable timestamps are not yet supported by this datasource.
    case _: TimestampNTZNanosType | _: TimestampLTZNanosType => false
    ```
    
    Sources changed:
    - V1 `FileFormat.supportDataType`: `ParquetFileFormat`, `OrcFileFormat` (native), `JsonFileFormat`, `XmlFileFormat`, and the private `CSVFileFormat.supportDataType(dataType, allowVariant)` (covers both `supportDataType` and `supportReadDataType`).
    - `AvroUtils.supportsDataType` (single edit covering V1 `AvroFileFormat` and V2 `AvroTable`).
    - `sql/hive` `OrcFileFormat.supportDataType` (Hive ORC serde).
    - V2 `FileTable.supportsDataType`: `ParquetTable`, `OrcTable`, `JsonTable`, `CSVTable`.
    
    No code change was needed for:
    - Text (string-only, already rejects nanos).
    - JDBC read (`getCatalystType` maps JDBC `TIMESTAMP`/`TIME` to microsecond types only).
    - JDBC write (already rejected via the `CreatableRelationProvider` type whitelist and `JdbcUtils.getCommonJDBCType` returning `None`); a test is added to lock the behavior.
    - XML V2: there is no `XmlTable`; XML is V1-only, so the `XmlFileFormat` edit covers all XML paths.
    
    The rejection is **unconditional** (independent of the `spark.sql.timestampNanosTypes.enabled` preview flag). The flag governs only whether the type may exist; these sources have no real nanos read/write support yet. As each source adds support later (e.g. Parquet read via SPARK-57102), it can carve out its own exception.
    
    This is a sub-task of [SPARK-56822](https://issues.apache.org/jira/browse/SPARK-56822) (SPIP: Timestamps with nanosecond precision).
    
    ### Why are the changes needed?
    
    When the preview flag `spark.sql.timestampNanosTypes.enabled` is on, the nanos types extend `DatetimeType extends AtomicType`, so every file source's `case _: AtomicType => true` catch-all silently **accepts** them and then misbehaves at read/write time because no real support exists yet. Users get confusing downstream failures or silent precision issues instead of a clear, actionable error. This PR provides a clear guardrail until real support is implemented per source.
    
    ### Does this PR introduce _any_ user-facing change?
    
    Yes, but only for the preview feature gated by `spark.sql.timestampNanosTypes.enabled` (disabled by default in production). With the flag enabled, writing or reading a `TIMESTAMP_NTZ(p)` / `TIMESTAMP_LTZ(p)` (p in [7, 9]) column through Parquet, ORC, Avro, JSON, CSV, XML (v1 and v2), and Hive ORC now fails with `UNSUPPORTED_DATA_TYPE_FOR_DATASOURCE`, and JDBC write fails with a clear error. Previously these were silently accepted and then misbehaved. Behavior for all other types is un [...]
    
    ### How was this patch tested?
    
    Added unit tests:
    - `FileBasedDataSourceSuite`: a new test that, with `TIMESTAMP_NANOS_TYPES_ENABLED=true`, iterates over v1 and v2 (`USE_V1_SOURCE_LIST`) and the built-in formats (parquet, orc, json, csv, xml), asserting that both write and read of a `TIMESTAMP_NTZ(9)` / `TIMESTAMP_LTZ(9)` column fail with `UNSUPPORTED_DATA_TYPE_FOR_DATASOURCE`. The nanos column is built from a typed literal rather than via CAST.
    - `AvroSuite`: an equivalent write+read assertion for Avro.
    - `JDBCWriteSuite`: a new test asserting that writing a nanos column via `.write.jdbc(...)` is rejected.
    
    All three suites pass locally, and `sql/core`, `sql/hive`, and `connector/avro` test sources compile.
    
    ### Was this patch authored or co-authored using generative AI tooling?
    
    Generated-by: Cursor (Claude Opus 4.8)
    
    Closes #56269 from MaxGekk/nanos-ds-off.
    
    Authored-by: Maxim Gekk <ma...@gmail.com>
    Signed-off-by: Max Gekk <ma...@gmail.com>
    (cherry picked from commit fa57fa2f5e9cfe97ab44e8af59b3ce15c906a0f9)
    Signed-off-by: Max Gekk <ma...@gmail.com>
---
 .../org/apache/spark/sql/avro/AvroSuite.scala      | 43 +++++++++++++++-
 .../org/apache/spark/sql/avro/AvroUtils.scala      |  3 ++
 .../execution/datasources/csv/CSVFileFormat.scala  |  3 ++
 .../datasources/json/JsonFileFormat.scala          |  3 ++
 .../execution/datasources/orc/OrcFileFormat.scala  |  3 ++
 .../datasources/parquet/ParquetFileFormat.scala    |  3 ++
 .../execution/datasources/v2/csv/CSVTable.scala    |  5 +-
 .../execution/datasources/v2/json/JsonTable.scala  |  3 ++
 .../execution/datasources/v2/orc/OrcTable.scala    |  3 ++
 .../datasources/v2/parquet/ParquetTable.scala      |  3 ++
 .../execution/datasources/xml/XmlFileFormat.scala  |  3 ++
 .../spark/sql/FileBasedDataSourceSuite.scala       | 60 ++++++++++++++++++++++
 .../org/apache/spark/sql/jdbc/JDBCWriteSuite.scala | 25 ++++++++-
 .../apache/spark/sql/hive/orc/OrcFileFormat.scala  |  2 +
 .../spark/sql/hive/orc/HiveOrcSourceSuite.scala    | 44 +++++++++++++++-
 15 files changed, 202 insertions(+), 4 deletions(-)

diff --git a/connector/avro/src/test/scala/org/apache/spark/sql/avro/AvroSuite.scala b/connector/avro/src/test/scala/org/apache/spark/sql/avro/AvroSuite.scala
index 56b107c14f57..c680850e602c 100644
--- a/connector/avro/src/test/scala/org/apache/spark/sql/avro/AvroSuite.scala
+++ b/connector/avro/src/test/scala/org/apache/spark/sql/avro/AvroSuite.scala
@@ -38,7 +38,7 @@ import org.apache.spark.TestUtils.assertExceptionMsg
 import org.apache.spark.sql._
 import org.apache.spark.sql.TestingUDT.IntervalData
 import org.apache.spark.sql.avro.AvroCompressionCodec._
-import org.apache.spark.sql.catalyst.expressions.AttributeReference
+import org.apache.spark.sql.catalyst.expressions.{AttributeReference, Literal}
 import org.apache.spark.sql.catalyst.plans.logical.Filter
 import org.apache.spark.sql.catalyst.util.DateTimeTestUtils
 import org.apache.spark.sql.catalyst.util.DateTimeTestUtils.{withDefaultTimeZone, LA, UTC}
@@ -52,6 +52,7 @@ import org.apache.spark.sql.internal.SQLConf
 import org.apache.spark.sql.test.SharedSparkSession
 import org.apache.spark.sql.types._
 import org.apache.spark.sql.v2.avro.AvroScan
+import org.apache.spark.unsafe.types.TimestampNanosVal
 import org.apache.spark.util.Utils
 
 abstract class AvroSuite
@@ -3191,6 +3192,46 @@ abstract class AvroSuite
     }
   }
 
+  test("SPARK-57166: nanosecond timestamp types are not supported in Avro") {
+    val nanosTypes = Seq(TimestampNTZNanosType(9), TimestampLTZNanosType(9))
+    withSQLConf(SQLConf.TIMESTAMP_NANOS_TYPES_ENABLED.key -> "true") {
+      nanosTypes.foreach { nanosType =>
+        val expectedType = s""""${nanosType.sql}""""
+        withTempDir { dir =>
+          // Write path: a nanos-typed column cannot be written. The nanos literal is built
+          // directly from its internal value to avoid relying on cast/parser support.
+          val nanosLiteral = Literal.create(new TimestampNanosVal(0L, 0.toShort), nanosType)
+          val df = spark.range(1).select(Column(nanosLiteral).as("ts"))
+          val writeDir = new File(dir, "write").getCanonicalPath
+          checkError(
+            exception = intercept[AnalysisException] {
+              df.write.format("avro").mode("overwrite").save(writeDir)
+            },
+            condition = "UNSUPPORTED_DATA_TYPE_FOR_DATASOURCE",
+            parameters = Map(
+              "columnName" -> "`ts`",
+              "columnType" -> expectedType,
+              "format" -> "Avro"))
+
+          // Read path: a user-specified nanos schema is rejected. Write a benign file first
+          // so schema validation (not file listing) is what fails.
+          val readDir = new File(dir, "read").getCanonicalPath
+          Seq("a").toDF("ts").write.format("avro").mode("overwrite").save(readDir)
+          checkError(
+            exception = intercept[AnalysisException] {
+              spark.read.schema(new StructType().add("ts", nanosType))
+                .format("avro").load(readDir).collect()
+            },
+            condition = "UNSUPPORTED_DATA_TYPE_FOR_DATASOURCE",
+            parameters = Map(
+              "columnName" -> "`ts`",
+              "columnType" -> expectedType,
+              "format" -> "Avro"))
+        }
+      }
+    }
+  }
+
 }
 
 class AvroV1Suite extends AvroSuite {
diff --git a/sql/core/src/main/scala/org/apache/spark/sql/avro/AvroUtils.scala b/sql/core/src/main/scala/org/apache/spark/sql/avro/AvroUtils.scala
index 02220e6c8568..7019e925080c 100644
--- a/sql/core/src/main/scala/org/apache/spark/sql/avro/AvroUtils.scala
+++ b/sql/core/src/main/scala/org/apache/spark/sql/avro/AvroUtils.scala
@@ -109,6 +109,9 @@ private[sql] object AvroUtils extends Logging {
 
     case _: GeometryType | _: GeographyType => false
 
+    // Nanosecond-capable timestamps are not yet supported by this datasource.
+    case _: TimestampNTZNanosType | _: TimestampLTZNanosType => false
+
     case _: AtomicType => true
 
     case st: StructType => st.forall { f => supportsDataType(f.dataType) }
diff --git a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/csv/CSVFileFormat.scala b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/csv/CSVFileFormat.scala
index 77a0c53ae469..988223abe8bc 100644
--- a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/csv/CSVFileFormat.scala
+++ b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/csv/CSVFileFormat.scala
@@ -157,6 +157,9 @@ case class CSVFileFormat() extends TextBasedFileFormat with DataSourceRegister {
 
     case _: GeometryType | _: GeographyType => false
 
+    // Nanosecond-capable timestamps are not yet supported by this datasource.
+    case _: TimestampNTZNanosType | _: TimestampLTZNanosType => false
+
     case _: AtomicType => true
 
     case udt: UserDefinedType[_] => supportDataType(udt.sqlType)
diff --git a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/json/JsonFileFormat.scala b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/json/JsonFileFormat.scala
index 2a8708f72ab1..331230c4446b 100644
--- a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/json/JsonFileFormat.scala
+++ b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/json/JsonFileFormat.scala
@@ -117,6 +117,9 @@ case class JsonFileFormat() extends TextBasedFileFormat with DataSourceRegister
 
     case _: GeometryType | _: GeographyType => false
 
+    // Nanosecond-capable timestamps are not yet supported by this datasource.
+    case _: TimestampNTZNanosType | _: TimestampLTZNanosType => false
+
     case _: AtomicType => true
 
     case st: StructType => st.forall { f => supportDataType(f.dataType) }
diff --git a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/orc/OrcFileFormat.scala b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/orc/OrcFileFormat.scala
index 633ac8107f32..8b926f51efc3 100644
--- a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/orc/OrcFileFormat.scala
+++ b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/orc/OrcFileFormat.scala
@@ -251,6 +251,9 @@ class OrcFileFormat
 
     case _: GeometryType | _: GeographyType => false
 
+    // Nanosecond-capable timestamps are not yet supported by this datasource.
+    case _: TimestampNTZNanosType | _: TimestampLTZNanosType => false
+
     case _: AtomicType => true
 
     case st: StructType => st.forall { f => supportDataType(f.dataType) }
diff --git a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetFileFormat.scala b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetFileFormat.scala
index a00618a1e2e2..669d826ddac4 100644
--- a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetFileFormat.scala
+++ b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/parquet/ParquetFileFormat.scala
@@ -416,6 +416,9 @@ class ParquetFileFormat
     case g: GeometryType => GeometryType.isSridSupported(g.srid)
     case g: GeographyType => GeographyType.isSridSupported(g.srid)
 
+    // Nanosecond-capable timestamps are not yet supported by this datasource.
+    case _: TimestampNTZNanosType | _: TimestampLTZNanosType => false
+
     case _: AtomicType | _: NullType => true
 
     case st: StructType => st.forall { f => supportDataType(f.dataType) }
diff --git a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/csv/CSVTable.scala b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/csv/CSVTable.scala
index 4938df795cb1..221196c86445 100644
--- a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/csv/CSVTable.scala
+++ b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/csv/CSVTable.scala
@@ -26,7 +26,7 @@ import org.apache.spark.sql.connector.write.{LogicalWriteInfo, Write, WriteBuild
 import org.apache.spark.sql.execution.datasources.FileFormat
 import org.apache.spark.sql.execution.datasources.csv.CSVDataSource
 import org.apache.spark.sql.execution.datasources.v2.FileTable
-import org.apache.spark.sql.types.{AtomicType, DataType, GeographyType, GeometryType, StructType, UserDefinedType}
+import org.apache.spark.sql.types.{AtomicType, DataType, GeographyType, GeometryType, StructType, TimestampLTZNanosType, TimestampNTZNanosType, UserDefinedType}
 import org.apache.spark.sql.util.CaseInsensitiveStringMap
 
 case class CSVTable(
@@ -59,6 +59,9 @@ case class CSVTable(
   override def supportsDataType(dataType: DataType): Boolean = dataType match {
     case _: GeometryType | _: GeographyType => false
 
+    // Nanosecond-capable timestamps are not yet supported by this datasource.
+    case _: TimestampNTZNanosType | _: TimestampLTZNanosType => false
+
     case _: AtomicType => true
 
     case udt: UserDefinedType[_] => supportsDataType(udt.sqlType)
diff --git a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/json/JsonTable.scala b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/json/JsonTable.scala
index cf3c1e11803c..dc3729a119b1 100644
--- a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/json/JsonTable.scala
+++ b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/json/JsonTable.scala
@@ -59,6 +59,9 @@ case class JsonTable(
   override def supportsDataType(dataType: DataType): Boolean = dataType match {
     case _: GeometryType | _: GeographyType => false
 
+    // Nanosecond-capable timestamps are not yet supported by this datasource.
+    case _: TimestampNTZNanosType | _: TimestampLTZNanosType => false
+
     case _: AtomicType => true
 
     case st: StructType => st.forall { f => supportsDataType(f.dataType) }
diff --git a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/orc/OrcTable.scala b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/orc/OrcTable.scala
index 08cd89fdacc6..e595a680d2e6 100644
--- a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/orc/OrcTable.scala
+++ b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/orc/OrcTable.scala
@@ -53,6 +53,9 @@ case class OrcTable(
   override def supportsDataType(dataType: DataType): Boolean = dataType match {
     case _: GeometryType | _: GeographyType => false
 
+    // Nanosecond-capable timestamps are not yet supported by this datasource.
+    case _: TimestampNTZNanosType | _: TimestampLTZNanosType => false
+
     case _: AtomicType => true
 
     case st: StructType => st.forall { f => supportsDataType(f.dataType) }
diff --git a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/parquet/ParquetTable.scala b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/parquet/ParquetTable.scala
index 67052c201a9d..9c3b05504363 100644
--- a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/parquet/ParquetTable.scala
+++ b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/parquet/ParquetTable.scala
@@ -55,6 +55,9 @@ case class ParquetTable(
     case g: GeometryType => GeometryType.isSridSupported(g.srid)
     case g: GeographyType => GeographyType.isSridSupported(g.srid)
 
+    // Nanosecond-capable timestamps are not yet supported by this datasource.
+    case _: TimestampNTZNanosType | _: TimestampLTZNanosType => false
+
     case _: AtomicType => true
 
     case st: StructType => st.forall { f => supportsDataType(f.dataType) }
diff --git a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/xml/XmlFileFormat.scala b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/xml/XmlFileFormat.scala
index 686d81a0b56d..f32b915fc054 100644
--- a/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/xml/XmlFileFormat.scala
+++ b/sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/xml/XmlFileFormat.scala
@@ -134,6 +134,9 @@ case class XmlFileFormat() extends TextBasedFileFormat with DataSourceRegister {
 
     case _: GeometryType | _: GeographyType => false
 
+    // Nanosecond-capable timestamps are not yet supported by this datasource.
+    case _: TimestampNTZNanosType | _: TimestampLTZNanosType => false
+
     case _: AtomicType => true
 
     case st: StructType => st.forall { f => supportDataType(f.dataType) }
diff --git a/sql/core/src/test/scala/org/apache/spark/sql/FileBasedDataSourceSuite.scala b/sql/core/src/test/scala/org/apache/spark/sql/FileBasedDataSourceSuite.scala
index 1fc45e9703f9..e5f64545986f 100644
--- a/sql/core/src/test/scala/org/apache/spark/sql/FileBasedDataSourceSuite.scala
+++ b/sql/core/src/test/scala/org/apache/spark/sql/FileBasedDataSourceSuite.scala
@@ -44,6 +44,7 @@ import org.apache.spark.sql.functions._
 import org.apache.spark.sql.internal.SQLConf
 import org.apache.spark.sql.test.SharedSparkSession
 import org.apache.spark.sql.types._
+import org.apache.spark.unsafe.types.TimestampNanosVal
 
 class FileBasedDataSourceSuite extends SharedSparkSession
   with AdaptiveSparkPlanHelper {
@@ -1334,6 +1335,65 @@ class FileBasedDataSourceSuite extends SharedSparkSession
       }
     }
   }
+
+  test("SPARK-57166: nanosecond timestamp types are not supported in file data sources") {
+    // None of these built-in file formats support nanosecond-capable timestamps yet.
+    val unsupportedDataSources = Seq("parquet", "orc", "json", "csv", "xml")
+    val nanosTypes = Seq(TimestampNTZNanosType(9), TimestampLTZNanosType(9))
+    withSQLConf(SQLConf.TIMESTAMP_NANOS_TYPES_ENABLED.key -> "true") {
+      // Test both v1 and v2 data sources.
+      Seq(true, false).foreach { useV1 =>
+        val useV1List = if (useV1) {
+          unsupportedDataSources.mkString(",")
+        } else {
+          ""
+        }
+        withSQLConf(SQLConf.USE_V1_SOURCE_LIST.key -> useV1List) {
+          unsupportedDataSources.foreach { format =>
+            nanosTypes.foreach { nanosType =>
+              val expectedType = s""""${nanosType.sql}""""
+              withTempDir { dir =>
+                // Write path: a nanos-typed column cannot be written. The nanos literal is built
+                // directly from its internal value to avoid relying on cast/parser support.
+                val nanosLiteral =
+                  Literal.create(new TimestampNanosVal(0L, 0.toShort), nanosType)
+                val df = spark.range(1).select(Column(nanosLiteral).as("ts"))
+                val writeDir = new File(dir, "write").getCanonicalPath
+                checkError(
+                  exception = intercept[AnalysisException] {
+                    df.write.format(format).mode("overwrite").save(writeDir)
+                  },
+                  condition = "UNSUPPORTED_DATA_TYPE_FOR_DATASOURCE",
+                  parameters = Map(
+                    "columnName" -> "`ts`",
+                    "columnType" -> expectedType,
+                    "format" -> formatMapping(format)))
+
+                // Read path: a user-specified nanos schema is rejected. Write a benign file first
+                // so schema validation (not file listing) is what fails.
+                val readDir = new File(dir, "read").getCanonicalPath
+                // XML requires a `rowTag` option on both the read and write paths.
+                val extraOptions =
+                  if (format == "xml") Map("rowTag" -> "row") else Map.empty[String, String]
+                Seq("a").toDF("ts").write.format(format).options(extraOptions)
+                  .mode("overwrite").save(readDir)
+                checkError(
+                  exception = intercept[AnalysisException] {
+                    spark.read.schema(new StructType().add("ts", nanosType))
+                      .format(format).options(extraOptions).load(readDir).collect()
+                  },
+                  condition = "UNSUPPORTED_DATA_TYPE_FOR_DATASOURCE",
+                  parameters = Map(
+                    "columnName" -> "`ts`",
+                    "columnType" -> expectedType,
+                    "format" -> formatMapping(format)))
+              }
+            }
+          }
+        }
+      }
+    }
+  }
 }
 
 object TestingUDT {
diff --git a/sql/core/src/test/scala/org/apache/spark/sql/jdbc/JDBCWriteSuite.scala b/sql/core/src/test/scala/org/apache/spark/sql/jdbc/JDBCWriteSuite.scala
index 535b8257ad94..bce7a883fa2b 100644
--- a/sql/core/src/test/scala/org/apache/spark/sql/jdbc/JDBCWriteSuite.scala
+++ b/sql/core/src/test/scala/org/apache/spark/sql/jdbc/JDBCWriteSuite.scala
@@ -28,12 +28,15 @@ import org.scalatest.BeforeAndAfter
 
 import org.apache.spark.SparkException
 import org.apache.spark.scheduler.{SparkListener, SparkListenerTaskEnd}
-import org.apache.spark.sql.{AnalysisException, DataFrame, Row, SaveMode}
+import org.apache.spark.sql.{AnalysisException, Column, DataFrame, Row, SaveMode}
+import org.apache.spark.sql.catalyst.expressions.Literal
 import org.apache.spark.sql.catalyst.parser.ParseException
+import org.apache.spark.sql.classic.ClassicConversions._
 import org.apache.spark.sql.execution.datasources.jdbc.{JDBCOptions, JdbcUtils}
 import org.apache.spark.sql.internal.SQLConf
 import org.apache.spark.sql.test.SharedSparkSession
 import org.apache.spark.sql.types._
+import org.apache.spark.unsafe.types.TimestampNanosVal
 import org.apache.spark.util.ArrayImplicits._
 import org.apache.spark.util.Utils
 
@@ -697,4 +700,24 @@ class JDBCWriteSuite extends SharedSparkSession with BeforeAndAfter {
     assert(rows(0).getAs[java.sql.Timestamp](1)
       === java.sql.Timestamp.valueOf("2020-02-02 04:13:14.56789"))
   }
+
+  test("SPARK-57166: nanosecond timestamp types are not supported in JDBC write") {
+    withSQLConf(SQLConf.TIMESTAMP_NANOS_TYPES_ENABLED.key -> "true") {
+      Seq(TimestampNTZNanosType(9), TimestampLTZNanosType(9)).foreach { nanosType =>
+        // The nanos literal is built directly from its internal value to avoid relying on
+        // cast/parser support.
+        val nanosLiteral = Literal.create(new TimestampNanosVal(0L, 0.toShort), nanosType)
+        val df = spark.range(1).select(Column(nanosLiteral).as("ts"))
+        checkErrorMatchPVals(
+          exception = intercept[AnalysisException] {
+            df.write.jdbc(url, "TEST.NANOSTYPES", new Properties())
+          },
+          condition = "UNSUPPORTED_DATA_TYPE_FOR_DATASOURCE",
+          parameters = Map(
+            "columnName" -> "`ts`",
+            "columnType" -> java.util.regex.Pattern.quote(s""""${nanosType.sql}""""),
+            "format" -> ".*"))
+      }
+    }
+  }
 }
diff --git a/sql/hive/src/main/scala/org/apache/spark/sql/hive/orc/OrcFileFormat.scala b/sql/hive/src/main/scala/org/apache/spark/sql/hive/orc/OrcFileFormat.scala
index ba37e5c176de..b8961e0ee84e 100644
--- a/sql/hive/src/main/scala/org/apache/spark/sql/hive/orc/OrcFileFormat.scala
+++ b/sql/hive/src/main/scala/org/apache/spark/sql/hive/orc/OrcFileFormat.scala
@@ -197,6 +197,8 @@ case class OrcFileFormat() extends FileFormat
 
     case _: AnsiIntervalType => false
     case _: TimeType => false
+    // Nanosecond-capable timestamps are not yet supported by this datasource.
+    case _: TimestampNTZNanosType | _: TimestampLTZNanosType => false
     case _: AtomicType => true
 
     case st: StructType => st.forall { f => supportDataType(f.dataType) }
diff --git a/sql/hive/src/test/scala/org/apache/spark/sql/hive/orc/HiveOrcSourceSuite.scala b/sql/hive/src/test/scala/org/apache/spark/sql/hive/orc/HiveOrcSourceSuite.scala
index c1084dd4ee7f..504d1a5881a0 100644
--- a/sql/hive/src/test/scala/org/apache/spark/sql/hive/orc/HiveOrcSourceSuite.scala
+++ b/sql/hive/src/test/scala/org/apache/spark/sql/hive/orc/HiveOrcSourceSuite.scala
@@ -19,12 +19,16 @@ package org.apache.spark.sql.hive.orc
 
 import java.io.File
 
-import org.apache.spark.sql.{AnalysisException, Row}
+import org.apache.spark.sql.{AnalysisException, Column, Row}
 import org.apache.spark.sql.TestingUDT.{IntervalData, IntervalUDT}
+import org.apache.spark.sql.catalyst.expressions.Literal
+import org.apache.spark.sql.classic.ClassicConversions._
 import org.apache.spark.sql.execution.datasources.orc.OrcSuite
 import org.apache.spark.sql.hive.HiveUtils
 import org.apache.spark.sql.hive.test.TestHiveSingleton
+import org.apache.spark.sql.internal.SQLConf
 import org.apache.spark.sql.types._
+import org.apache.spark.unsafe.types.TimestampNanosVal
 import org.apache.spark.util.Utils
 
 class HiveOrcSourceSuite extends OrcSuite with TestHiveSingleton {
@@ -346,6 +350,44 @@ class HiveOrcSourceSuite extends OrcSuite with TestHiveSingleton {
     }
   }
 
+  test("SPARK-57166: nanosecond timestamp types are not supported in Hive ORC") {
+    val nanosTypes = Seq(TimestampNTZNanosType(9), TimestampLTZNanosType(9))
+    withSQLConf(SQLConf.TIMESTAMP_NANOS_TYPES_ENABLED.key -> "true") {
+      nanosTypes.foreach { nanosType =>
+        val expectedType = s""""${nanosType.sql}""""
+        withTempDir { dir =>
+          // Write path
+          val nanosLiteral = Literal.create(new TimestampNanosVal(0L, 0.toShort), nanosType)
+          val df = spark.range(1).select(Column(nanosLiteral).as("ts"))
+          val writeDir = new File(dir, "write").getCanonicalPath
+          checkError(
+            exception = intercept[AnalysisException] {
+              df.write.format("orc").mode("overwrite").save(writeDir)
+            },
+            condition = "UNSUPPORTED_DATA_TYPE_FOR_DATASOURCE",
+            parameters = Map(
+              "columnName" -> "`ts`",
+              "columnType" -> expectedType,
+              "format" -> "ORC"))
+
+          // Read path
+          val readDir = new File(dir, "read").getCanonicalPath
+          spark.range(1).write.format("orc").mode("overwrite").save(readDir)
+          checkError(
+            exception = intercept[AnalysisException] {
+              spark.read.schema(new StructType().add("ts", nanosType))
+                .format("orc").load(readDir).collect()
+            },
+            condition = "UNSUPPORTED_DATA_TYPE_FOR_DATASOURCE",
+            parameters = Map(
+              "columnName" -> "`ts`",
+              "columnType" -> expectedType,
+              "format" -> "ORC"))
+        }
+      }
+    }
+  }
+
   test("SPARK-31580: Read a file written before ORC-569") {
     // Test ORC file came from ORC-621
     val df = readResourceOrcFile("test-data/TestStringDictionary.testRowIndex.orc")


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@spark.apache.org
For additional commands, e-mail: commits-help@spark.apache.org