cloud-fan commented on code in PR #58549:
URL: https://github.com/apache/spark/pull/58549#discussion_r3991646192
##########
sql/core/src/main/scala/org/apache/spark/sql/classic/SparkSession.scala:
##########
@@ -887,11 +887,21 @@ class SparkSession private(
private[sql] def applySchemaToPythonRDD(
rdd: RDD[Array[Any]],
schema: StructType): DataFrame = {
+ val applyCharVarcharChecks =
Review Comment:
**Blocking (P1):** `outputSchema` is selected with the boolean captured
above, but this lazy closure calls the overload that reads `SQLConf` again when
an action runs. If the DataFrame is created under standard semantics and
collected after that scope, it can put unchecked/unpadded values into the
retained CHAR/VARCHAR schema; the reverse transition can unexpectedly pad or
reject values for an output already normalized to STRING. Please pass the
captured `applyCharVarcharChecks` into `makeFromJava` here and cover
create-under-one-policy/collect-under-the-other in both directions.
##########
python/pyspark/sql/connect/udtf.py:
##########
@@ -166,10 +171,32 @@ def __init__(
self._name = name or func.__name__
self.evalType = evalType
self.deterministic = deterministic
+ self._validated_return_type_session_ids: Set[str] = set()
+
+ def _check_return_type(self, session: "SparkSession") -> None:
+ if self.returnType is None or self.evalType not in (
Review Comment:
**Blocking (P1):** This early return skips the new recursive return-type
rejection for every regular `SQL_TABLE_UDF`. The shared parity suite calls a
regular UDTF with nested CHAR/VARCHAR inside `assertRaises`, but Connect only
builds a lazy DataFrame here, so that assertion cannot fire later during server
analysis. Could we validate already-materialized `StructType` values for
regular UDTFs before returning, while preserving the no-`ddl_parse`-RPC path
for DDL strings?
##########
sql/core/src/test/scala/org/apache/spark/sql/IntegratedUDFTestUtils.scala:
##########
@@ -443,12 +443,31 @@ object IntegratedUDFTestUtils extends SQLHelper {
children: Seq[Expression],
evalType: Int,
udfDeterministic: Boolean,
- resultId: ExprId)
- extends PythonUDF(name, func, dataType, children, evalType,
udfDeterministic, resultId) {
+ resultId: ExprId,
+ elementwiseNestingDepth: Int,
+ applyCharVarcharChecks: Boolean)
Review Comment:
**Blocking (P1):** `PythonUDFWithoutId` copies the neighboring nesting-depth
and policy fields but not `hasCharVarcharResult`, so `TestTypedScalarPandasUDF`
resets the marker to false. The new true-columnar CHAR/VARCHAR cases then take
the unchecked path instead of the production fallback that applies padding and
overflow checks, either failing or validating the wrong route. Please forward
the wrapped UDF's marker as well.
##########
sql/core/src/main/scala/org/apache/spark/sql/execution/python/ExtractPythonUDFFromLambda.scala:
##########
@@ -441,7 +441,8 @@ object ExtractPythonUDFFromLambda extends Rule[LogicalPlan]
{
// `PythonUDF.liftedElementwiseEvalType`.
PythonUDF.liftedElementwiseEvalType(udf.evalType),
udf.udfDeterministic,
- elementwiseNestingDepth = newDepth)
+ elementwiseNestingDepth = newDepth,
+ applyCharVarcharChecks = udf.applyCharVarcharChecks)
Review Comment:
**Blocking (P1):** The lifted UDF also needs to preserve
`udf.hasCharVarcharResult`. Leaving it at the false default makes an
Arrow-backed `transform` look unconstrained to
`ColumnarArrowEvalPythonEvaluatorFactory`, which can then take
`evalArrowColumnar` and bypass the checked output projection. That leaves
under-length CHAR unpadded and accepts over-length VARCHAR. Please copy the
marker and add an Arrow-backed higher-order-function regression for both
outcomes.
##########
sql/core/src/main/scala/org/apache/spark/sql/execution/python/streaming/TransformWithStateInPySparkStateServer.scala:
##########
@@ -80,6 +82,14 @@ class TransformWithStateInPySparkStateServer(
import PythonResponseWriterUtils._
+ private def validateStateSchema(schema: StructType, schemaKind: String):
Unit = {
+ if (CharVarcharUtils.hasCharVarchar(schema)) {
+ throw QueryCompilationErrors.invalidPythonStateSchema(schema, schemaKind)
+ }
+ }
+
+ validateStateSchema(groupingKeySchema, "grouping key")
Review Comment:
**Blocking (P1):** This constructor check does not run on either production
runner's synchronous initialization path. Both open the state-server socket
first and construct this object inside a listener thread; if this throws, that
thread exits before `run()`/`accept()`, while the Python worker can wait on the
still-advertised but unserved socket and the driver pre-init waits for the
worker response. Please validate the immutable grouping-key schema
synchronously before socket publication/worker startup in both runner paths,
while keeping dynamic value/list/map-state validation in the live request loop.
**Recommended change:** Move or duplicate grouping-key schema validation to
a shared runner-side preflight that runs before initStateServer and worker
startup, and ensure the server constructor cannot be the first place that
rejects the grouping schema.
**Why this works:** Validate the immutable groupingKeySchema on the calling
runner thread, throw the classed unsupported-schema error before allocating or
advertising a socket, and leave request-time validation for dynamically
registered value/list/map state schemas inside the serving loop where errors
are returned over the protocol.
**Scope:**
sql/core/src/main/scala/org/apache/spark/sql/execution/python/streaming,
sql/core/src/test/scala/org/apache/spark/sql/execution/python/streaming
**Compatibility:** Supported TransformWithStateInPySpark initialization and
request-time state schema errors continue to use their existing worker and
socket protocols.
**Risks:** The driver pre-init and executor runner could drift if validation
is duplicated rather than shared. Moving all state-schema validation out of the
server would be incorrect because value/list/map schemas arrive dynamically in
requests.
**Constraints:** Use the existing recursive CharVarcharUtils predicate and
classed invalidPythonStateSchema error. Apply the preflight to both driver
pre-initialization and executor compute paths. Do not open or expose the
state-server socket before the grouping-key preflight succeeds.
**Success:** An unsupported grouping-key schema fails synchronously with
UNSUPPORTED_FEATURE.PYTHON_STATE_CHAR_VARCHAR_SCHEMA. No Python worker waits on
an unserved state-server socket after that failure. Supported grouping schemas
retain current driver and executor state-server lifecycle. Dynamic unsupported
value/list/map state schemas still receive an error response through the live
state-server protocol.
##########
sql/core/src/main/scala/org/apache/spark/sql/execution/python/ColumnarArrowEvalPythonEvaluatorFactory.scala:
##########
@@ -151,15 +173,17 @@ private[python] class
ColumnarArrowEvalPythonEvaluatorFactory(
batch.column(0).isInstanceOf[ArrowColumnVector]
}
- if (inputColumnIndices.isDefined && isArrow) {
+ if (inputColumnIndices.isDefined && isArrow && !hasCharVarcharOutput) {
Review Comment:
**Non-blocking (P2):** `hasCharVarcharResult` remains true even when
`applyCharVarcharChecks` is false and the resolved result type is already
STRING, so this condition unnecessarily removes the full Arrow-columnar path
from legacy UDFs. In the fallback below, the same marker also replaces valid
`inputColumnIndices` with `None`, forcing Path 3's per-row `MutableProjection`,
although Path 2 converges on the same `checkedOutput` projection. Please gate
the full-columnar exclusion on checks actually being active and retain direct
indices for checked outputs.
--
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]