srielau commented on PR #58549:
URL: https://github.com/apache/spark/pull/58549#issuecomment-5717981303
## Agent working brief (scope freeze)
This PR has the same review loop as #58581: a shared Python/Arrow mapper
admits CHAR/VARCHAR globally, then each review round requires the next consumer
to either enforce assignment semantics or reject the type. **Do not continue
filling that matrix.**
CHAR and VARCHAR are SQL types. At a Python/Arrow boundary, "support" means
values that re-enter Catalyst obey CHAR/VARCHAR assignment under the policy
captured at analysis, not ambient conf at execute.
This is SPARK-59275 / PR 58549. It is **not** Connect schema transport (that
is SPARK-59276 / #58581). Do not change Connect logical/physical schema
handling here except where this PR already touches a Python UDF/UDTF client
check that this fence still owns.
### Success condition
Merge when all of the following are true:
1. With `spark.sql.charVarchar.standardSemantics.enabled=true`, scalar
Python UDFs (row, Arrow-optimized, pandas scalar) accept CHAR/VARCHAR return
types and apply padding / VARCHAR length checks before values re-enter Catalyst.
2. Higher-order-function UDFs that reuse that scalar path (`transform`,
etc.) keep the captured policy through fusion and lambda lift.
3. Explicit-schema local, pandas, and PyArrow DataFrame creation apply the
same recursive assignment checks.
4. `DataFrame.toArrow()` exports CHAR/VARCHAR values as Arrow strings.
5. Nested struct / array / map work on those supported paths.
6. Default and `legacy.charVarcharAsString` behavior does not newly expose
first-class CHAR/VARCHAR where the pre-PR surface was STRING or an error.
7. Every other public eval type this mapper now reaches **recursively
rejects** CHAR/VARCHAR with a deterministic validation error (not unchecked
values).
8. No new configuration is added.
If a later review asks for anything outside this list, reply that it is a
follow-up, do not implement it here, and stop.
### Fence
A surface is in this PR only if all four hold:
1. Users routinely declare CHAR/VARCHAR as a UDF, createDataFrame, or
toArrow type.
2. Without the change, standard-semantics CHAR/VARCHAR is either rejected or
silently wrong.
3. This PR can both **admit** the type and **enforce** assignment (or
toArrow export) on that path.
4. The path is not an extension protocol (UDT, DataSource,
TransformWithState) unless this PR is only **rejecting** it.
Test question: **Would this still be required if Arrow UDTFs, row UDTFs,
Python DataSource, TransformWithState, and CHAR/VARCHAR inside UDT.sqlType did
not exist?** If no, it is out of scope.
#### In scope (admit + enforce)
- Scalar Python UDFs: row, Arrow-optimized, pandas scalar.
- Higher-order function UDFs that reuse that scalar path, including copying
captured policy through fusion and lambda lift.
- Explicit-schema local / pandas / PyArrow DataFrame creation.
- `DataFrame.toArrow()` exporting CHAR/VARCHAR as Arrow strings.
- Nested struct / array / map on those paths.
- Policy: standard semantics on; default/legacy must still look like STRING
(or the pre-PR error).
- Exhaustive **rejection** of CHAR/VARCHAR (recursive) on every other public
eval type the shared mapper now reaches.
#### Out of scope (reject now, follow-up JIRA later)
- Arrow UDTFs and row UDTFs (fixed and analyze-derived).
- Python DataSource return schemas.
- TransformWithState / state-server grouping and state schemas.
- CHAR/VARCHAR inside `UserDefinedType.sqlType`.
- Full assignment enforcement for grouped-map, cogroup, aggregate, and
iterator-agg eval types (unless JVM `stringLengthCheck` already exists on that
exec; if not, reject).
- Connect logical/physical CHAR/VARCHAR transport (PR 58581).
- New configs. Existing CHAR/VARCHAR flags are enough.
Unsupported must mean a **deterministic validation error**, not "the mapper
allows it and the JVM happens to work."
### Required architecture
```text
Python/Arrow boundary
|
+-- supported surface? -- yes --> physical STRING in Arrow
| logical CHAR/VARCHAR in Spark
schema
| assignment check with CAPTURED
policy
|
+-- no --> recursive reject before exec
```
Rules:
- `pandas/types.py` (and similar converters) are **physical encoding**, not
the capability API.
- Capability is an explicit allow-list in `udf.py` / creation / `toArrow`.
Everything else rejects recursively.
- Do not use ambient `SQLConf` at execute for a UDF whose policy was
captured at analysis.
- Do not put UDT unwrap / UDT-storage CHAR semantics into this PR except as
part of rejection, or to undo a break this branch caused.
### What to undo or freeze on current head
Keep scalar UDF + createDataFrame + `toArrow` enforcement and the
captured-policy plumbing those paths need.
Stop adding, and revert if they are only there as product support:
- TransformWithState state-server preflight as CHAR/VARCHAR support (reject
grouping/state CHAR/VARCHAR instead).
- UDT-storage recursion as support (do not invent UDT CHAR assignment here).
- Connect UDTF "complete validation" beyond recursive reject.
- Extra policy flags whose only clients are out-of-scope surfaces.
Tests that belong here:
- One case per **supported** surface x standard / default / legacy.
- Nested struct/array/map on the supported UDF and createDataFrame paths.
- One rejection case per **unsupported** public eval type the mapper can
reach.
Do not add a test because an agent mentioned TWS, analyze-UDTF, UDT.sqlType,
mixed-batch, or RDD-only branches unless that production code remains after the
freeze. If the code is out of scope, delete the path change rather than
covering it.
### How to handle reviews
Public GitHub reviews posted as `cloud-fan` with `SPARK_DEV_REVIEW_CODE`,
and silent internal-review commits, are optional input. They are not a backlog
you must empty.
For each finding, classify:
| Class | Action |
|---|---|
| In-scope bug on scalar UDF, HOF UDF, createDataFrame, or toArrow | Fix it.
|
| Mapper now admits an unsupported eval type | Reject recursively; do not
implement assignment. |
| Regression this branch introduced | Restore prior behavior. Do not
"complete" the new behavior. |
| UDT.sqlType, DataSource, TWS, UDTF assignment, grouped-map completeness |
Follow-up. Comment and stop. |
| Captured vs ambient policy on a **supported** path (fusion, lambda lift) |
Fix it. |
| Captured vs ambient policy only needed for an out-of-scope path | Remove
that path from support. |
**Do not run an internal-review pass that implements findings without
posting the classification.** Silent fix commits are how this PR ballooned. Do
not use `--auto` on this self-PR.
The Sep 8 review already recommended this narrower boundary. Freeze there.
### Stop conditions (mandatory)
Stop and ask instead of coding when:
- A review demands CHAR/VARCHAR assignment for UDTFs, DataSource, or
TransformWithState.
- A review demands UDT.sqlType recursion as support.
- A review demands grouped/cogroup/aggregate CHAR assignment rather than
rejection.
- Fixing an in-scope bug seems to require one of the above.
- You are about to treat `to_arrow_type` as a global capability flag again.
### Follow-up tickets (mention, do not implement)
1. CHAR/VARCHAR assignment for Arrow and row Python UDTFs.
2. Python DataSource CHAR/VARCHAR return schemas.
3. TransformWithState grouping/state CHAR/VARCHAR.
4. CHAR/VARCHAR inside UDT storage types.
5. Grouped-map / cogroup / aggregate CHAR/VARCHAR assignment.
The next change on this branch should **narrow to this fence**, then wait
for a human before another review-fix cycle.
Related: #58581 owns Connect schema transport and must not keep a
conflicting global Arrow CHAR/VARCHAR mapper. This PR owns the mapper, behind
the allow-list above.
--
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]