Skip to content

[SPARK-58794][SQL] Promote CHAR/VARCHAR to STRING via ImplicitTypeCasts - #58130

Closed
srielau wants to merge 6 commits into
apache:masterfrom
srielau:serge-rielau_data/SPARK-58794-r1-implicit
Closed

[SPARK-58794][SQL] Promote CHAR/VARCHAR to STRING via ImplicitTypeCasts#58130
srielau wants to merge 6 commits into
apache:masterfrom
srielau:serge-rielau_data/SPARK-58794-r1-implicit

Conversation

@srielau

@srielausrielau commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

Under spark.sql.charVarchar.standardSemantics.enabled, an expression that expects a plain string now gets CHAR(n) / VARCHAR(n) promoted to unbounded STRING by the analyzer, the same way SHORT is promoted to INT. Previously each transforming expression stripped the length constraint in its own dataType.

  • Move charVarcharToPlainString onto TypeCoercionHelper and recurse into arrays when the expected type is AbstractArrayType / TypeCollection (for example, array_join).
  • Apply the promotion in ImplicitTypeCasts for both ImplicitCastInputTypes and ExpectsInputTypes.
  • Use collation-preserving implicitCastToString for Concat / Elt instead of implicitCast(e, StringType), which missed collated CHAR.
  • Drop the per-expression StringHelper.transformingStringResultType stripping; a result type now follows its promoted child.
  • Use ExpectsInputTypes for StringSplitSQL.
  • Give JsonTuple a CHAR/VARCHAR-only coercion arm so its existing INT and untyped NULL validation remains unchanged.

Why are the changes needed?

Stripping the constraint inside each expression missed every ExpectsInputTypes site (str_to_map, array_join, and others) and collated CHAR on Concat, and it had to be repeated in each new expression. Promoting at the call site is the same mechanism Spark already uses for numeric widening, so it applies uniformly and shows up in the analyzed plan as a CAST(... AS STRING).

Does this PR introduce any user-facing change?

Yes, when the flag is on: analyzed plans for transforming string operators now contain an explicit CAST(... AS STRING) and those operators return unbounded STRING. Result values are unchanged versus the previous per-expression stripping.

How was this patch tested?

  • TypeCoercionSuite: legacy and ANSI Concat / Elt promotion, including CHAR(n) COLLATE UTF8_LCASE; ImplicitTypeCasts promotion; and unchanged JsonTuple INT / untyped NULL handling.
  • BasicCharVarcharTestSuite: transforming string result types and collated Concat / Elt values.
  • Regenerated charvarchar-standard-semantics.sql result and analyzer goldens.

Was this patch authored or co-authored using generative AI tooling?

Yes (Cursor).

@srielau
srielauforce-pushed the serge-rielau_data/SPARK-58794-r1-implicit branch from 562c515 to 9f6e38eCompareAugust 19, 2026 17:03
@srielausrielau changed the title [SPARK-58794][SQL] Implement R1 CHAR/VARCHAR promotion via ImplicitTypeCasts[SPARK-58794][SQL] Promote CHAR/VARCHAR to STRING via ImplicitTypeCastsAug 19, 2026
@srielau
srielauforce-pushed the serge-rielau_data/SPARK-58794-r1-implicit branch 2 times, most recently from 453ca20 to 5d82a3bCompareAugust 19, 2026 19:43
@srielau

Copy link
Copy Markdown
ContributorAuthor

Rebased onto #58087 (6dc3909). Analyzer goldens updated for concat promotion after the LCT CHAR-pad plan change.

@srielau
srielauforce-pushed the serge-rielau_data/SPARK-58794-r1-implicit branch 3 times, most recently from 5c5cc1b to 9586d5fCompareAugust 19, 2026 23:13
cloud-fan pushed a commit that referenced this pull request Aug 20, 2026
…esult schemas
### What changes were proposed in this pull request?
Covers Connect JDBC mapping ([SPARK-58806](https://issues.apache.org/jira/browse/SPARK-58806)), plus HiveServer2 / JDBC result metadata ([SPARK-58804](https://issues.apache.org/jira/browse/SPARK-58804)) and `DatabaseMetaData.getColumns` `COLUMN_SIZE` ([SPARK-58805](https://issues.apache.org/jira/browse/SPARK-58805)). Parent: [SPARK-58794](https://issues.apache.org/jira/browse/SPARK-58794).
Independent of the remaining CHAR/VARCHAR follow-ups. Rebased onto `master` after #58033; can merge without #58080 / #58087 / #58130. Unique vs master: srielau/spark@master...serge-rielau_data/SPARK-58794-clients
Expose first-class `CHAR(n)` and `VARCHAR(n)` to SQL clients when `spark.sql.charVarchar.standardSemantics.enabled` is on, and let those clients decode result rows that carry the types.
Metadata:
- Spark Connect JDBC maps `CharType` / `VarcharType` to `java.sql.Types.CHAR` / `VARCHAR`, with Java string values and precision / display size `n`.
- Connect `DatabaseMetaData.getColumns` reports `COLUMN_SIZE` as the declared character length `n`. `CHAR_OCTET_LENGTH` is the UTF-8 maximum byte capacity `4 * n` (saturating at `Int.MaxValue`); unbounded STRING stays 0.
- Connect `DatabaseMetaData.getTypeInfo` lists `CHAR` and `VARCHAR` with `CREATE_PARAMS = length`. Unbounded `STRING` remains a `Types.VARCHAR` row without create params.
- HiveServer2 `getColumns` reports `COLUMN_SIZE = n` for `VARCHAR(n)` as well as `CHAR(n)`, and `CHAR_OCTET_LENGTH = 4 * n` for both (null for unbounded STRING).
- HiveServer2 result metadata coverage pins Hive JDBC names (`char` / `varchar`, no length) and precision `n` for CAST results.
Decode (JVM Connect client):
- The Connect client process has no engine `SQLConf`, so `RowEncoder.encoderFor` still follows the local `charVarcharFirstClassTypes` flag and rejects CHAR/VARCHAR when first-class types are off.
- `RowEncoder.encoderForResultSchema` always accepts CHAR/VARCHAR. `SparkResult.createEncoder` uses it only for `UnboundRowEncoder` (engine-produced result schemas). Other encoder bindings keep `encoderFor`.
- Arrow serializer / deserializer treat `CharEncoder` / `VarcharEncoder` like `StringEncoder`, and deserialize via `StringHelper.plainStringType` so STRING Arrow vectors up-cast to CHAR/VARCHAR.
- JDBC CAST `ResultSet.next` / `collect` needs this path; metadata-only mapping is not enough.
Python Connect already maps proto `char` / `var_char` in `pyspark.sql.connect.types`; a mixed classic vs Connect test covers schema and `collect()`.
The existing Connect proto already carries CHAR/VARCHAR and their lengths.
### Why are the changes needed?
Spark Connect JDBC rejected first-class `CharType` and `VarcharType` as unsupported because its metadata mapping only recognized the `StringType` singleton. `getColumns` hardcoded `CHAR_OCTET_LENGTH` to 0, and `getTypeInfo` listed only unbounded STRING. HiveServer2 already identified VARCHAR catalog columns but reported `COLUMN_SIZE` as unknown. Clients therefore cannot reliably describe or size CHAR/VARCHAR columns even though Catalyst retains the type and length.
Separately, a server with standard semantics sends CHAR/VARCHAR in the result schema. The JVM Connect client then failed at row decode (`UNSUPPORTED_DATA_TYPE_FOR_ENCODER`) because `RowEncoder.encoderFor` read the client's default conf (`charVarcharFirstClassTypes = false`). JDBC CAST collect and Spark Connect `SparkResult` both hit that path.
### Does this PR introduce _any_ user-facing change?
Yes, when first-class CHAR/VARCHAR types are present: JDBC and HiveServer2 metadata now report the corresponding JDBC type and declared character length instead of rejecting the type or reporting an unknown size. `CHAR_OCTET_LENGTH` is the UTF-8 maximum byte capacity (`4 * n`), not the character length. Connect clients can also collect CHAR/VARCHAR result columns instead of failing to decode the schema.
### How was this patch tested?
- `RowEncoderSuite`: `encoderFor` still raises `UNSUPPORTED_DATA_TYPE_FOR_ENCODER` for CHAR/VARCHAR when both first-class flags are off; `encoderForResultSchema` accepts the same schema.
- `ArrowEncoderSuite`: CHAR/VARCHAR Arrow round-trip (top-level, nested struct, array).
- `SparkConnectJdbcDataTypeSuite`: CAST result metadata and table `getColumns` (`CHAR(4)` / `VARCHAR(6)`, `COLUMN_SIZE`, `CHAR_OCTET_LENGTH` = 16 / 24); CAST collect after decode.
- `SparkConnectDatabaseMetaDataSuite`: `getTypeInfo` rows for CHAR / VARCHAR.
- `SparkMetadataOperationSuite`: HiveServer2 `getColumns` / CAST result metadata (`CHAR_OCTET_LENGTH` = `4 * n`).
- `pyspark.sql.tests.connect.test_connect_basic.SparkConnectBasicTests.test_char_varchar_result_schema`: classic vs Connect schema and `collect()`.
Local compile of `sql-api`, `connect-client-jdbc`, and `hive-thriftserver` passed. HiveServer2 / full Connect JDBC in this environment remain blocked (`CheckReturnValue` while compiling `connect-common`; FIPS Python multiprocessing). Please treat the suites above as the merge gate.
### Was this patch authored or co-authored using generative AI tooling?
Yes (Cursor).
Closes#58132 from srielau/serge-rielau_data/SPARK-58794-clients.
Authored-by: Serge Rielau <serge@rielau.com>
Signed-off-by: Wenchen Fan <wenchen@databricks.com>
cloud-fan pushed a commit that referenced this pull request Aug 20, 2026
…esult schemas
### What changes were proposed in this pull request?
Covers Connect JDBC mapping ([SPARK-58806](https://issues.apache.org/jira/browse/SPARK-58806)), plus HiveServer2 / JDBC result metadata ([SPARK-58804](https://issues.apache.org/jira/browse/SPARK-58804)) and `DatabaseMetaData.getColumns` `COLUMN_SIZE` ([SPARK-58805](https://issues.apache.org/jira/browse/SPARK-58805)). Parent: [SPARK-58794](https://issues.apache.org/jira/browse/SPARK-58794).
Independent of the remaining CHAR/VARCHAR follow-ups. Rebased onto `master` after #58033; can merge without #58080 / #58087 / #58130. Unique vs master: srielau/spark@master...serge-rielau_data/SPARK-58794-clients
Expose first-class `CHAR(n)` and `VARCHAR(n)` to SQL clients when `spark.sql.charVarchar.standardSemantics.enabled` is on, and let those clients decode result rows that carry the types.
Metadata:
- Spark Connect JDBC maps `CharType` / `VarcharType` to `java.sql.Types.CHAR` / `VARCHAR`, with Java string values and precision / display size `n`.
- Connect `DatabaseMetaData.getColumns` reports `COLUMN_SIZE` as the declared character length `n`. `CHAR_OCTET_LENGTH` is the UTF-8 maximum byte capacity `4 * n` (saturating at `Int.MaxValue`); unbounded STRING stays 0.
- Connect `DatabaseMetaData.getTypeInfo` lists `CHAR` and `VARCHAR` with `CREATE_PARAMS = length`. Unbounded `STRING` remains a `Types.VARCHAR` row without create params.
- HiveServer2 `getColumns` reports `COLUMN_SIZE = n` for `VARCHAR(n)` as well as `CHAR(n)`, and `CHAR_OCTET_LENGTH = 4 * n` for both (null for unbounded STRING).
- HiveServer2 result metadata coverage pins Hive JDBC names (`char` / `varchar`, no length) and precision `n` for CAST results.
Decode (JVM Connect client):
- The Connect client process has no engine `SQLConf`, so `RowEncoder.encoderFor` still follows the local `charVarcharFirstClassTypes` flag and rejects CHAR/VARCHAR when first-class types are off.
- `RowEncoder.encoderForResultSchema` always accepts CHAR/VARCHAR. `SparkResult.createEncoder` uses it only for `UnboundRowEncoder` (engine-produced result schemas). Other encoder bindings keep `encoderFor`.
- Arrow serializer / deserializer treat `CharEncoder` / `VarcharEncoder` like `StringEncoder`, and deserialize via `StringHelper.plainStringType` so STRING Arrow vectors up-cast to CHAR/VARCHAR.
- JDBC CAST `ResultSet.next` / `collect` needs this path; metadata-only mapping is not enough.
Python Connect already maps proto `char` / `var_char` in `pyspark.sql.connect.types`; a mixed classic vs Connect test covers schema and `collect()`.
The existing Connect proto already carries CHAR/VARCHAR and their lengths.
### Why are the changes needed?
Spark Connect JDBC rejected first-class `CharType` and `VarcharType` as unsupported because its metadata mapping only recognized the `StringType` singleton. `getColumns` hardcoded `CHAR_OCTET_LENGTH` to 0, and `getTypeInfo` listed only unbounded STRING. HiveServer2 already identified VARCHAR catalog columns but reported `COLUMN_SIZE` as unknown. Clients therefore cannot reliably describe or size CHAR/VARCHAR columns even though Catalyst retains the type and length.
Separately, a server with standard semantics sends CHAR/VARCHAR in the result schema. The JVM Connect client then failed at row decode (`UNSUPPORTED_DATA_TYPE_FOR_ENCODER`) because `RowEncoder.encoderFor` read the client's default conf (`charVarcharFirstClassTypes = false`). JDBC CAST collect and Spark Connect `SparkResult` both hit that path.
### Does this PR introduce _any_ user-facing change?
Yes, when first-class CHAR/VARCHAR types are present: JDBC and HiveServer2 metadata now report the corresponding JDBC type and declared character length instead of rejecting the type or reporting an unknown size. `CHAR_OCTET_LENGTH` is the UTF-8 maximum byte capacity (`4 * n`), not the character length. Connect clients can also collect CHAR/VARCHAR result columns instead of failing to decode the schema.
### How was this patch tested?
- `RowEncoderSuite`: `encoderFor` still raises `UNSUPPORTED_DATA_TYPE_FOR_ENCODER` for CHAR/VARCHAR when both first-class flags are off; `encoderForResultSchema` accepts the same schema.
- `ArrowEncoderSuite`: CHAR/VARCHAR Arrow round-trip (top-level, nested struct, array).
- `SparkConnectJdbcDataTypeSuite`: CAST result metadata and table `getColumns` (`CHAR(4)` / `VARCHAR(6)`, `COLUMN_SIZE`, `CHAR_OCTET_LENGTH` = 16 / 24); CAST collect after decode.
- `SparkConnectDatabaseMetaDataSuite`: `getTypeInfo` rows for CHAR / VARCHAR.
- `SparkMetadataOperationSuite`: HiveServer2 `getColumns` / CAST result metadata (`CHAR_OCTET_LENGTH` = `4 * n`).
- `pyspark.sql.tests.connect.test_connect_basic.SparkConnectBasicTests.test_char_varchar_result_schema`: classic vs Connect schema and `collect()`.
Local compile of `sql-api`, `connect-client-jdbc`, and `hive-thriftserver` passed. HiveServer2 / full Connect JDBC in this environment remain blocked (`CheckReturnValue` while compiling `connect-common`; FIPS Python multiprocessing). Please treat the suites above as the merge gate.
### Was this patch authored or co-authored using generative AI tooling?
Yes (Cursor).
Closes#58132 from srielau/serge-rielau_data/SPARK-58794-clients.
Authored-by: Serge Rielau <serge@rielau.com>
Signed-off-by: Wenchen Fan <wenchen@databricks.com>
(cherry picked from commit 6af294e)
Signed-off-by: Wenchen Fan <wenchen@databricks.com>
Where an expression expects a plain string, promote CHAR(n)/VARCHAR(n) to
unbounded STRING in the analyzer the same way SHORT promotes to INT, rather
than stripping the length constraint in each expression's result type.
@srielau
srielauforce-pushed the serge-rielau_data/SPARK-58794-r1-implicit branch from 9586d5f to 7871f0fCompareAugust 22, 2026 16:42

@srielausrielau left a comment

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SQL Language review (7871f0f)

Promoting CHAR/VARCHAR to unbounded STRING in implicitCast, before acceptsType, is the right place. CHAR subclasses StringType, so the old per-expression transformingStringResultType was papering over a type-coercion hole. Gating on charVarcharStandardSemantics, skipping AnyDataType, and leaving LCT / explicit CAST / store assignment alone looks correct. Array recursion for array_join is justified. Concat/Elt still need their own rule because they bypass generic ImplicitTypeCasts. Dual-run coverage on shared TypeCoercionHelper is the right resolver story.

Findings

  1. High -- JsonTuple now does general implicit casts, unflagged. Mixing ImplicitCastInputTypes is not CHAR promotion. json_tuple('{"a": 1}', 1) and json_tuple('{"a": 1}', null) currently fail DATATYPE_MISMATCH.NON_STRING_TYPE (generators.sql, table-valued-functions.sql). After this change they succeed (INT/NULL -> STRING), even with standardSemantics off. JsonTable mixed in the same trait on purpose so untyped NULL reaches runtime; JsonTuple goldens encode the opposite contract.

    Use ExpectsInputTypes (not ImplicitCastInputTypes) and keep inputTypes. The new ExpectsInputTypes arm already promotes CHAR/VARCHAR without opening numeric/NULL casts.

  2. Medium -- implicitCastToString duplicates the new promotion. Flag check + plainStringType already live in charVarcharToPlainString. Concat/Elt can call that (or implicitCast(e, StringTypeWithCollation(supportsTrimCollation = true))) and fall back to implicitCast(e, StringType) for non-strings.

  3. Medium -- collated Concat/Elt is the stated reason for implicitCastToString and is untested. New Concat/Upper tests use UTF8_BINARY CHAR only. Please add CHAR(...) COLLATE UTF8_LCASE for Concat and Elt (and ANSI coercion).

  4. Low -- StringSplitSQL does not need ImplicitCastInputTypes.split_part already has it. Prefer ExpectsInputTypes here too, same as JsonTuple.

Architecture of the central promotion is sound. Finding 1 is the blocker: it changes SQL outside the flag and contradicts existing goldens this PR does not regenerate.

Promote CHAR/VARCHAR through ExpectsInputTypes and charVarcharToPlainString
instead of mixing ImplicitCastInputTypes on JsonTuple and StringSplitSQL.

@srielausrielau left a comment

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SQL Language re-review (f0675dd)

Previous items:

  1. JsonTuple / StringSplitSQL: switched to ExpectsInputTypes. INT no longer implicit-casts. Good.
  2. implicitCastToString: now delegates to charVarcharToPlainString. Good.
  3. Collated Concat/Elt: TypeCoercionSuite (ANSI + non-ANSI), CharVarcharTestSuite, and goldens cover UTF8_LCASE. Good.

Remaining

  1. High -- json_tuple(..., null) is still a contract change. Mixing ExpectsInputTypes opts JsonTuple into the existing NullType rewrite in ImplicitTypeCoercion:

    Literal.create(null, expected.defaultConcreteType)

    Untyped null becomes a STRING null beforecheckInputDataTypes. select json_tuple('{"a": 1}', null) currently fails NON_STRING_TYPE in generators.sql / table-valued-functions.sql. INT is fixed; NULL is not. This is also unflagged (standardSemantics off).

    Options: (a) do not mix ExpectsInputTypes and promote CHAR only in a JsonTuple-specific arm that skips the NullType rewrite; (b) accept the NULL change and regenerate those goldens. (a) matches the previous review.

  2. Low -- PR description is stale. It still says mix ImplicitCastInputTypes into JsonTuple and StringSplitSQL.

The extra CHAR(2) -> CHAR(3) in the new collated concat analyzer golden comes from CollationTypeCoercion on Concat children (LCT), not from this promotion. Not blocking here.

Limit JsonTuple coercion to CHAR/VARCHAR promotion so untyped NULL and
non-string field names retain their existing analysis errors.

@srielausrielau left a comment

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SQL Language re-review (4132d65)

The JsonTuple NULL issue is fixed. JsonTuple no longer mixes ExpectsInputTypes / ImplicitCastInputTypes. CHAR/VARCHAR promotion is a dedicated ImplicitTypeCoercion arm that only applies charVarcharToPlainString, so untyped NULL and INT stay for checkInputDataTypes (NON_STRING_TYPE). Rule tests pin all three: NULL unchanged, INT unchanged, CHAR -> CAST(... AS STRING) under the flag. ANSI/non-ANSI share TypeCoercionHelper, so both paths get the arm.

Previous mediums (shared implicitCastToString, collated Concat/Elt coverage) remain in good shape.

Remaining (non-blocking)

  1. Low -- PR description is still stale. It still says mix ImplicitCastInputTypes into JsonTuple and StringSplitSQL. The actual contract is: dedicated JsonTuple arm; StringSplitSQL uses ExpectsInputTypes.

  2. Low -- JsonTuple rule tests sit in cast NullType for expressions that implement ExpectsInputTypes. JsonTuple is no longer that. A one-line comment on the case j: JsonTuple arm (custom checkInputDataTypes; do not take the NullType rewrite) would also help the next editor.

No new architectural issues. Not blocking on the lows.

Explain at the match arm, not just on the class, that JsonTuple must not be
folded into the ExpectsInputTypes arm, since that would revive the NullType
rewrite its own checkInputDataTypes is meant to reject. Move the JsonTuple
rule tests out of the ExpectsInputTypes test case, which no longer describes
them.
@srielau

Copy link
Copy Markdown
ContributorAuthor

Thanks for the re-review. Both lows are addressed in ff948fd.

1. PR description. This one was already fixed -- the description was updated at the same minute the review was posted, so you were reading the previous snapshot. It currently reads:

  • Use ExpectsInputTypes for StringSplitSQL.
  • Give JsonTuple a CHAR/VARCHAR-only coercion arm so its existing INT and untyped NULL validation remains unchanged.

2. JsonTuple tests and the arm comment. The rule tests moved out of cast NullType for expressions that implement ExpectsInputTypes into their own coerce JsonTuple children without the NullType rewrite, which is what they actually pin: NULL unchanged, INT unchanged, CHAR promoted under the flag. The Upper CHAR case stays behind in the original test, since that one really is an ExpectsInputTypes expression.

The arm itself now carries the warning:

// JsonTuple validates its own input types and rejects non-string children with// NON_STRING_TYPE, so it only takes the CHAR/VARCHAR promotion here. Do not fold this into// the ExpectsInputTypes arm below: that would also apply the NullType rewrite and turn// json_tuple(json, null) from an analysis error into a typed STRING null.

TypeCoercionSuite and AnsiTypeCoercionSuite pass (108 tests).

@cloud-fancloud-fan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 blocking, 2 non-blocking, 0 nits.
The coercion design is sound, with two non-blocking comment and test-integrity issues to address.

Correctness (2)

  • Non-blocking: sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/stringExpressions.scala:2710: Update the Right comment: both analyzers inspect unresolved RuntimeReplaceable replacements, so COMPLEX_EXPRESSION_UNSUPPORTED_INPUT is not the mismatch failure path. -- see inline
  • Non-blocking: sql/core/src/test/scala/org/apache/spark/sql/CharVarcharTestSuite.scala:1848: Make the CHAR/VARCHAR analyzer-parity test assert that each query actually enters dual-run; ResolverGuard rejection currently falls back to fixed-point silently. -- see inline

Verification

I traced the promotion from the legacy, ANSI, and single-pass analyzer entry points into the shared TypeCoercionHelper, including the configuration gate, collation preservation, array recursion, and the dedicated JsonTuple path that leaves INT and untyped NULL for its validator. I also checked the changed rule/value/golden expectations against those paths. I did not run tests during this review.

@cloud-fancloud-fan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 addressed, 2 remaining, 0 new to this AI review.
0 blocking, 2 non-blocking, 0 nits.
The coercion design is sound; two non-blocking comment and test-integrity issues remain.

Remaining from prior review (2)

  • Non-blocking: Remove the inaccurate analyzer-failure claim from Right; both analyzers reject an unresolved replacement during analysis, so COMPLEX_EXPRESSION_UNSUPPORTED_INPUT is not the later failure path. -- existing thread
  • Non-blocking: Make the analyzer-parity matrix assert that each query actually enters dual-run; ResolverGuard can otherwise route it silently to fixed-point analysis. -- existing thread

Verification

I traced scalar and array promotion through legacy, ANSI, and single-pass coercion; confirmed the flag, collation, AnyDataType, and JsonTuple gates; and checked the changed expression metadata and test expectations against those paths. I also verified both analyzers reject unresolved RuntimeReplaceable replacements and that HybridAnalyzer can bypass dual-run when ResolverGuard returns an unsupported reason. I did not run tests during this review.

Remove an inaccurate RuntimeReplaceable failure-path comment and make the
analyzer parity matrix fail when ResolverGuard would skip any query.
@srielau

Copy link
Copy Markdown
ContributorAuthor

Thanks @cloud-fan -- addressed the two remaining items from #58130 (review) in de3f84f.

The targeted single-pass resolver agrees with fixed-point under standardSemantics test passes.

@cloud-fancloud-fan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 addressed, 0 remaining, 0 new to this AI review.
0 blocking, 0 non-blocking, 0 nits.
The current revision is ready from a code-review perspective; the previously reported concerns are addressed and no actionable correctness, design, performance, or test-coverage issues remain.

Verification

Reviewed the complete diff and traced the changed coercion helpers through their legacy, ANSI, fixed-point, and single-pass call paths. Checked constrained scalar and array promotion, TypeCollection fallback behavior, collation preservation, specialized Concat/Elt handling, JsonTuple error preservation, result-type derivation, and the updated unit, integration, and SQL golden test coverage. Tests were not run locally.

cloud-fan pushed a commit that referenced this pull request Aug 25, 2026
### What changes were proposed in this pull request?
Under `spark.sql.charVarchar.standardSemantics.enabled`, an expression that expects a plain string now gets `CHAR(n)` / `VARCHAR(n)` promoted to unbounded `STRING` by the analyzer, the same way `SHORT` is promoted to `INT`. Previously each transforming expression stripped the length constraint in its own `dataType`.
- Move `charVarcharToPlainString` onto `TypeCoercionHelper` and recurse into arrays when the expected type is `AbstractArrayType` / `TypeCollection` (for example, `array_join`).
- Apply the promotion in `ImplicitTypeCasts` for both `ImplicitCastInputTypes` and `ExpectsInputTypes`.
- Use collation-preserving `implicitCastToString` for `Concat` / `Elt` instead of `implicitCast(e, StringType)`, which missed collated CHAR.
- Drop the per-expression `StringHelper.transformingStringResultType` stripping; a result type now follows its promoted child.
- Use `ExpectsInputTypes` for `StringSplitSQL`.
- Give `JsonTuple` a CHAR/VARCHAR-only coercion arm so its existing INT and untyped NULL validation remains unchanged.
### Why are the changes needed?
Stripping the constraint inside each expression missed every `ExpectsInputTypes` site (`str_to_map`, `array_join`, and others) and collated CHAR on `Concat`, and it had to be repeated in each new expression. Promoting at the call site is the same mechanism Spark already uses for numeric widening, so it applies uniformly and shows up in the analyzed plan as a `CAST(... AS STRING)`.
### Does this PR introduce _any_ user-facing change?
Yes, when the flag is on: analyzed plans for transforming string operators now contain an explicit `CAST(... AS STRING)` and those operators return unbounded `STRING`. Result values are unchanged versus the previous per-expression stripping.
### How was this patch tested?
- `TypeCoercionSuite`: legacy and ANSI `Concat` / `Elt` promotion, including `CHAR(n) COLLATE UTF8_LCASE`; `ImplicitTypeCasts` promotion; and unchanged `JsonTuple` INT / untyped NULL handling.
- `BasicCharVarcharTestSuite`: transforming string result types and collated Concat / Elt values.
- Regenerated `charvarchar-standard-semantics.sql` result and analyzer goldens.
### Was this patch authored or co-authored using generative AI tooling?
Yes (Cursor).
Closes#58130 from srielau/serge-rielau_data/SPARK-58794-r1-implicit.
Authored-by: Serge Rielau <serge@rielau.com>
Signed-off-by: Wenchen Fan <wenchen@databricks.com>
(cherry picked from commit 85eb4c8)
Signed-off-by: Wenchen Fan <wenchen@databricks.com>
@cloud-fan

Copy link
Copy Markdown
Contributor

Merge Summary:

Posted by merge_spark_pr.py

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@srielau@cloud-fan