cloud-fan commented on code in PR #58317:
URL: https://github.com/apache/spark/pull/58317#discussion_r4009047658


##########
sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/FileFormat.scala:
##########
@@ -266,6 +295,25 @@ object FileFormat {
    */
   val OPTION_RETURNING_BATCH = "returning_batch"
 
+  /**
+   * Engine-private Hadoop configuration entry that transports the analyzed 
CHAR/VARCHAR scan mode
+   * across the legacy [[FileFormat.buildReaderWithPartitionValues]] 
signature. It is written by the
+   * mode-aware overload and read by formats that honor first-class 
CHAR/VARCHAR types. This is not
+   * a public option; the authoritative state is the typed plan field and 
overload parameter.
+   */
+  val CHAR_VARCHAR_SCAN_MODE = "__spark_sql_char_varchar_scan_mode"
+
+  /** Writes the CHAR/VARCHAR scan mode into `conf` under 
[[CHAR_VARCHAR_SCAN_MODE]]. */
+  private[sql] def setCharVarcharScanMode(
+      conf: Configuration, mode: CharVarcharScanMode): Unit = {
+    conf.set(CHAR_VARCHAR_SCAN_MODE, mode.toString)
+  }
+
+  /** Reads the CHAR/VARCHAR scan mode from `conf`, or `None` if no mode was 
bridged in. */
+  private[sql] def charVarcharScanMode(conf: Configuration): 
Option[CharVarcharScanMode] = {
+    Option(conf.get(CHAR_VARCHAR_SCAN_MODE)).map(CharVarcharScanMode.fromName)

Review Comment:
   Confirmed: caller-provided bridge values are removed and only the typed 
overload authors the private mode on its cloned configuration. Thanks.
   
   <!-- SPARK_DEV_REVIEW_REPLY 
{"feedback_id":"inline:3960197738","thread_id":"inline:3960197738","verdict_sha256":"9e8299073fe3a3fd317d35d14ddb819ddbbaf0c580a01f233a538f0836de3d9e"}
 -->



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/ApplyCharTypePadding.scala:
##########
@@ -51,34 +51,74 @@ object ApplyCharTypePadding extends Rule[LogicalPlan] {
   }
 
   override def apply(plan: LogicalPlan): LogicalPlan = {
+    val standardSemantics = conf.charVarcharStandardSemantics
+    val scanMode = CharVarcharScanMode(standardSemantics)
+
+    // Bind into case-class state, not a TreeNodeTag: `TreeNode.makeCopy` 
calls `copyTagsFrom`,
+    // so a tag survives canonicalization, but it does not participate in 
structural plan

Review Comment:
   Confirmed: the final comment now states only that TreeNodeTag does not 
participate in structural equality or sameResult. Thanks.
   
   <!-- SPARK_DEV_REVIEW_REPLY 
{"feedback_id":"inline:3960197749","thread_id":"inline:3960197749","verdict_sha256":"9e8299073fe3a3fd317d35d14ddb819ddbbaf0c580a01f233a538f0836de3d9e"}
 -->



##########
sql/core/src/test/scala/org/apache/spark/sql/execution/datasources/SaveIntoDataSourceCommandSuite.scala:
##########
@@ -69,7 +70,29 @@ class SaveIntoDataSourceCommandSuite extends 
SharedSparkSession {
     saveIntoDataSource(2)
     checkAnswer(loadData, Row(0) :: Row(1) :: Nil)
 
+    spark.catalog.clearCache()
     FakeV1DataSource.data = null
+
+    // Bound modes store Some(false)/Some(true) on LogicalRelation, so recache 
must match

Review Comment:
   Confirmed: the test comment now uses the current two-bound-mode 
representation. Thanks.
   
   <!-- SPARK_DEV_REVIEW_REPLY 
{"feedback_id":"inline:3960197755","thread_id":"inline:3960197755","verdict_sha256":"9e8299073fe3a3fd317d35d14ddb819ddbbaf0c580a01f233a538f0836de3d9e"}
 -->



##########
sql/catalyst/src/main/scala/org/apache/spark/sql/execution/datasources/v2/DataSourceV2Relation.scala:
##########
@@ -116,7 +116,10 @@ case class DataSourceV2Relation(
     catalog: Option[CatalogPlugin],
     identifier: Option[Identifier],
     options: CaseInsensitiveStringMap,
-    timeTravelSpec: Option[TimeTravelSpec] = None)
+    timeTravelSpec: Option[TimeTravelSpec] = None,
+    // Bound at analysis so sameResult / cache reuse distinguish preserve-only 
vs standard
+    // CHAR/VARCHAR scans. None means the relation was not analyzed under 
first-class types.
+    charVarcharScanMode: Option[CharVarcharScanMode] = None)

Review Comment:
   Confirmed: SharedState relation-cache hits clear only the prior scan mode 
for fresh-session rebinding, while persisted plans retain captured semantics. 
Thanks.
   
   <!-- SPARK_DEV_REVIEW_REPLY 
{"feedback_id":"inline:3962201879","thread_id":"inline:3962201879","verdict_sha256":"9e8299073fe3a3fd317d35d14ddb819ddbbaf0c580a01f233a538f0836de3d9e"}
 -->



##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/CharVarcharScanMode.scala:
##########
@@ -0,0 +1,52 @@
+/*
+ * 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.spark.sql.catalyst.util
+
+/**
+ * The CHAR/VARCHAR scan mode bound to a relation (and its scan) during 
analysis.
+ *
+ * A relation carries `Option[CharVarcharScanMode]`: `None` means the relation 
was not analyzed
+ * under first-class CHAR/VARCHAR types (native reader behavior), while a 
`Some` value pins the
+ * mode so that `sameResult` / cache reuse keep the two variants distinct.
+ */
+sealed trait CharVarcharScanMode
+
+object CharVarcharScanMode {
+  /**
+   * Preserve the native, constrained CHAR/VARCHAR types of the source (e.g. 
native ORC
+   * padding/truncation). Corresponds to preserve-only semantics.
+   */
+  case object PreserveNative extends CharVarcharScanMode
+
+  /**
+   * Request physical STRING from the source so Spark observes the original 
value and applies
+   * standard CHAR/VARCHAR length checks. Corresponds to standard semantics.
+   */
+  case object SparkStandard extends CharVarcharScanMode
+
+  /** Maps the boolean `spark.sql.charVarcharStandardSemantics` value to the 
typed mode. */
+  def apply(standardSemantics: Boolean): CharVarcharScanMode =
+    if (standardSemantics) SparkStandard else PreserveNative
+
+  /** Parses a mode from its `toString` name; the inverse of 
[[CharVarcharScanMode.toString]]. */
+  def fromName(name: String): CharVarcharScanMode = name match {
+    case "PreserveNative" => PreserveNative
+    case "SparkStandard" => SparkStandard
+    case other => throw new IllegalArgumentException(s"Unknown 
CharVarcharScanMode: $other")

Review Comment:
   Confirmed: the Scaladoc now names 
spark.sql.charVarchar.standardSemantics.enabled exactly. Thanks.
   
   <!-- SPARK_DEV_REVIEW_REPLY 
{"feedback_id":"inline:3962201885","thread_id":"inline:3962201885","verdict_sha256":"9e8299073fe3a3fd317d35d14ddb819ddbbaf0c580a01f233a538f0836de3d9e"}
 -->



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/FileFormat.scala:
##########
@@ -165,6 +166,34 @@ trait FileFormat {
     }
   }
 
+  /**
+   * Same as [[buildReaderWithPartitionValues]] but also carries the analyzed 
CHAR/VARCHAR scan
+   * mode. [[FileSourceScanExec]] calls this overload whenever the relation 
has a bound mode,
+   * regardless of the concrete file format.
+   *
+   * The default implementation bridges the mode across the legacy 
seven-argument signature: it
+   * clones the per-call Hadoop configuration, writes an engine-private entry 
with the explicit
+   * mode, then invokes the seven-argument method virtually. A format that 
honors first-class
+   * CHAR/VARCHAR types (e.g. 
[[org.apache.spark.sql.execution.datasources.orc.OrcFileFormat]])
+   * reads that entry in its seven-argument override, so existing subclasses 
keep their override
+   * and a call to `super` retains the bound mode. The Hadoop entry is only a 
transport across the
+   * legacy signature; the authoritative state remains the typed plan field 
and this parameter.
+   */
+  def buildReaderWithPartitionValues(

Review Comment:
   Confirmed: the typed mode, bridge key, and overload are private[sql], while 
the existing public FileFormat hook remains intact. Thanks.
   
   <!-- SPARK_DEV_REVIEW_REPLY 
{"feedback_id":"inline:3962201889","thread_id":"inline:3962201889","verdict_sha256":"9e8299073fe3a3fd317d35d14ddb819ddbbaf0c580a01f233a538f0836de3d9e"}
 -->



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to