srielau commented on code in PR #58549:
URL: https://github.com/apache/spark/pull/58549#discussion_r3960787892


##########
python/pyspark/sql/udf.py:
##########
@@ -314,9 +317,24 @@ def _conf_is_true(key: str, default: Optional[str] = None) 
-> bool:
 
     @staticmethod
     def _check_return_type(returnType: DataType, evalType: int) -> None:
+        char_varchar_supported_eval_types = (
+            PythonEvalType.SQL_ARROW_BATCHED_UDF,
+            PythonEvalType.SQL_SCALAR_PANDAS_UDF,
+            PythonEvalType.SQL_SCALAR_PANDAS_ITER_UDF,
+            PythonEvalType.SQL_SCALAR_ARROW_UDF,
+            PythonEvalType.SQL_SCALAR_ARROW_ITER_UDF,
+        )
+
+        def check_arrow_type() -> None:
+            if evalType not in char_varchar_supported_eval_types and _has_type(

Review Comment:
   Fixed in 3234e913fb6. Return-type validation now explicitly allows only the 
supported scalar eval types and recursively rejects CHAR/VARCHAR for every 
other eval type. Added focused stateful and incremental aggregate cases.



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/python/EvalPythonEvaluatorFactory.scala:
##########
@@ -36,6 +38,16 @@ abstract class EvalPythonEvaluatorFactory(
     output: Seq[Attribute])
   extends PartitionEvaluatorFactory[InternalRow, InternalRow] {
 
+  private val applyCharVarcharChecks =
+    CharVarcharUtils.shouldApplyWriteSideLengthCheck(SQLConf.get)
+  private val checkedOutput = if (applyCharVarcharChecks) {
+    childOutput ++ output.drop(childOutput.length).map { attr =>
+      CharVarcharUtils.stringLengthCheck(attr, attr.dataType)

Review Comment:
   Fixed in 3234e913fb6. Concrete evaluator factories now declare whether 
deserialization already owns CHAR/VARCHAR checks: batched evaluation opts out 
of the common projection, while Arrow paths retain it. Per-UDF conversion 
preserves exactly one check.



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/arrow/ArrowConverters.scala:
##########
@@ -577,7 +592,7 @@ private[sql] object ArrowConverters extends Logging {
         TaskContext.get())
 
       // Project/copy it. Otherwise, the Arrow column vectors will be closed 
and released out.
-      val proj = UnsafeProjection.create(attrs, attrs)
+      val proj = UnsafeProjection.create(checkedAttrs, attrs)

Review Comment:
   Fixed in 3234e913fb6. The Arrow row iterator now has idempotent explicit 
cleanup, and local relation materialization closes it in finally after rows are 
copied. Added an allocator regression for VARCHAR validation failure.



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/python/ColumnarArrowEvalPythonEvaluatorFactory.scala:
##########
@@ -79,6 +81,22 @@ private[python] class 
ColumnarArrowEvalPythonEvaluatorFactory(
     sessionUUID: Option[String])
   extends PartitionEvaluatorFactory[ColumnarBatch, ColumnarBatch] {
 
+  private val applyCharVarcharChecks =
+    CharVarcharUtils.shouldApplyWriteSideLengthCheck(SQLConf.get)
+  private val checkedOutput = if (applyCharVarcharChecks) {
+    childOutput ++ output.drop(childOutput.length).map { attr =>
+      CharVarcharUtils.stringLengthCheck(attr, attr.dataType)
+    }
+  } else {
+    output
+  }
+  private val hasCharVarcharOutput =
+    applyCharVarcharChecks &&
+      output.drop(childOutput.length).exists(attr => 
CharVarcharUtils.hasCharVarchar(attr.dataType))
+  private val physicalOutputSchema = CharVarcharUtils

Review Comment:
   Fixed in 3234e913fb6. Columnar Arrow physical schema normalization now 
recursively unwraps UserDefinedType values to sqlType before replacing 
CHAR/VARCHAR. Added a regression with an ExamplePointUDT sibling.



##########
python/pyspark/sql/udf.py:
##########
@@ -314,9 +317,24 @@ def _conf_is_true(key: str, default: Optional[str] = None) 
-> bool:
 
     @staticmethod
     def _check_return_type(returnType: DataType, evalType: int) -> None:
+        char_varchar_supported_eval_types = (
+            PythonEvalType.SQL_ARROW_BATCHED_UDF,
+            PythonEvalType.SQL_SCALAR_PANDAS_UDF,
+            PythonEvalType.SQL_SCALAR_PANDAS_ITER_UDF,

Review Comment:
   Added in 3234e913fb6. Owning-suite coverage now includes Pandas SCALAR_ITER 
and Arrow SCALAR plus SCALAR_ITER, each checking CHAR padding and VARCHAR 
overflow rejection.



##########
python/pyspark/sql/connect/udtf.py:
##########
@@ -167,9 +172,21 @@ def __init__(
         self.evalType = evalType
         self.deterministic = deterministic
 
+    def _check_return_type(self) -> None:
+        if self.returnType is None:

Review Comment:
   Fixed in 3234e913fb6. Both classic and Connect paths now validate the 
effective schema returned by analyze() for Arrow UDTFs, including nested 
CHAR/VARCHAR. Added a dynamic analyze() regression.



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/python/ColumnarArrowEvalPythonEvaluatorFactory.scala:
##########
@@ -79,6 +81,22 @@ private[python] class 
ColumnarArrowEvalPythonEvaluatorFactory(
     sessionUUID: Option[String])
   extends PartitionEvaluatorFactory[ColumnarBatch, ColumnarBatch] {
 
+  private val applyCharVarcharChecks =
+    CharVarcharUtils.shouldApplyWriteSideLengthCheck(SQLConf.get)

Review Comment:
   Fixed in 3234e913fb6. The resolved write-side policy is stored on each 
PythonUDF/PythonUDTF expression and consumed by physical evaluators instead of 
rereading ambient SQLConf. A view regression flips caller settings before 
execution.



##########
python/pyspark/sql/connect/udtf.py:
##########
@@ -167,9 +172,21 @@ def __init__(
         self.evalType = evalType
         self.deterministic = deterministic
 
+    def _check_return_type(self) -> None:
+        if self.returnType is None:
+            return
+        return_type = (
+            _parse_datatype_string(self.returnType.data_type_string)

Review Comment:
   Fixed in 3234e913fb6. Connect skips parsing entirely for non-Arrow UDTFs and 
caches successful Arrow validation per owning session. A mocked call-count test 
verifies zero regular-UDF RPCs and one repeated Arrow validation RPC.



##########
sql/core/src/main/scala/org/apache/spark/sql/execution/python/EvaluatePython.scala:
##########
@@ -146,10 +147,19 @@ object EvaluatePython {
    * Make a converter that converts `obj` to the type specified by the data 
type, or returns
    * null if the type of obj is unexpected. Because Python doesn't enforce the 
type.
    */
-  def makeFromJava(dataType: DataType): Any => Any =
-    
TypeApiOps(dataType).flatMap(_.makeFromJava).getOrElse(makeFromJavaDefault(dataType))
+  def makeFromJava(dataType: DataType): Any => Any = {
+    val applyCharVarcharChecks =
+      CharVarcharUtils.shouldApplyWriteSideLengthCheck(SQLConf.get)

Review Comment:
   Fixed in 3234e913fb6. The query-derived write-check policy is passed 
explicitly through the TransformWithState runner, state server, and 
deserializer for value, list, and map state. Added a legacy-policy state-server 
regression.



-- 
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