Uh oh!
There was an error while loading. Please reload this page.
[SPARK-58794][SQL] Promote CHAR/VARCHAR to STRING via ImplicitTypeCasts - #58130
[SPARK-58794][SQL] Promote CHAR/VARCHAR to STRING via ImplicitTypeCasts#58130srielau wants to merge 6 commits into
Conversation
562c515 to
9f6e38eCompare453ca20 to
5d82a3bComparesrielau
commented
Aug 19, 2026
Rebased onto #58087 ( |
5c5cc1b to
9586d5fCompare…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>
…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.
9586d5f to
7871f0fCompare
srielau
left a comment
There was a problem hiding this comment.
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
High --
JsonTuplenow does general implicit casts, unflagged. MixingImplicitCastInputTypesis not CHAR promotion.json_tuple('{"a": 1}', 1)andjson_tuple('{"a": 1}', null)currently failDATATYPE_MISMATCH.NON_STRING_TYPE(generators.sql,table-valued-functions.sql). After this change they succeed (INT/NULL -> STRING), even withstandardSemanticsoff.JsonTablemixed in the same trait on purpose so untyped NULL reaches runtime;JsonTuplegoldens encode the opposite contract.Use
ExpectsInputTypes(notImplicitCastInputTypes) and keepinputTypes. The newExpectsInputTypesarm already promotes CHAR/VARCHAR without opening numeric/NULL casts.Medium --
implicitCastToStringduplicates the new promotion. Flag check +plainStringTypealready live incharVarcharToPlainString. Concat/Elt can call that (orimplicitCast(e, StringTypeWithCollation(supportsTrimCollation = true))) and fall back toimplicitCast(e, StringType)for non-strings.Medium -- collated Concat/Elt is the stated reason for
implicitCastToStringand is untested. New Concat/Upper tests use UTF8_BINARY CHAR only. Please addCHAR(...) COLLATE UTF8_LCASEfor Concat and Elt (and ANSI coercion).Low --
StringSplitSQLdoes not needImplicitCastInputTypes.split_partalready has it. PreferExpectsInputTypeshere 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.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Promote CHAR/VARCHAR through ExpectsInputTypes and charVarcharToPlainString instead of mixing ImplicitCastInputTypes on JsonTuple and StringSplitSQL.
srielau
left a comment
There was a problem hiding this comment.
SQL Language re-review (f0675dd)
Previous items:
- JsonTuple / StringSplitSQL: switched to
ExpectsInputTypes. INT no longer implicit-casts. Good. implicitCastToString: now delegates tocharVarcharToPlainString. Good.- Collated Concat/Elt: TypeCoercionSuite (ANSI + non-ANSI), CharVarcharTestSuite, and goldens cover UTF8_LCASE. Good.
Remaining
High --
json_tuple(..., null)is still a contract change. MixingExpectsInputTypesopts JsonTuple into the existing NullType rewrite inImplicitTypeCoercion:Literal.create(null, expected.defaultConcreteType)Untyped
nullbecomes a STRING null beforecheckInputDataTypes.select json_tuple('{"a": 1}', null)currently failsNON_STRING_TYPEingenerators.sql/table-valued-functions.sql. INT is fixed; NULL is not. This is also unflagged (standardSemanticsoff).Options: (a) do not mix
ExpectsInputTypesand 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.Low -- PR description is stale. It still says mix
ImplicitCastInputTypesinto 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.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Limit JsonTuple coercion to CHAR/VARCHAR promotion so untyped NULL and non-string field names retain their existing analysis errors.
srielau
left a comment
There was a problem hiding this comment.
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)
Low -- PR description is still stale. It still says mix
ImplicitCastInputTypesinto JsonTuple and StringSplitSQL. The actual contract is: dedicated JsonTuple arm; StringSplitSQL usesExpectsInputTypes.Low -- JsonTuple rule tests sit in
cast NullType for expressions that implement ExpectsInputTypes. JsonTuple is no longer that. A one-line comment on thecase j: JsonTuplearm (customcheckInputDataTypes; do not take the NullType rewrite) would also help the next editor.
No new architectural issues. Not blocking on the lows.
Uh oh!
There was an error while loading. Please reload this page.
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
commented
Aug 22, 2026
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:
2. JsonTuple tests and the arm comment. The rule tests moved out of 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.
|
cloud-fan
left a comment
There was a problem hiding this comment.
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
Rightcomment: both analyzers inspect unresolved RuntimeReplaceable replacements, soCOMPLEX_EXPRESSION_UNSUPPORTED_INPUTis 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.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
cloud-fan
left a comment
There was a problem hiding this comment.
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, soCOMPLEX_EXPRESSION_UNSUPPORTED_INPUTis 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
commented
Aug 25, 2026
Thanks @cloud-fan -- addressed the two remaining items from #58130 (review) in de3f84f.
The targeted |
cloud-fan
left a comment
There was a problem hiding this comment.
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.
### 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>
What changes were proposed in this pull request?
Under
spark.sql.charVarchar.standardSemantics.enabled, an expression that expects a plain string now getsCHAR(n)/VARCHAR(n)promoted to unboundedSTRINGby the analyzer, the same waySHORTis promoted toINT. Previously each transforming expression stripped the length constraint in its owndataType.charVarcharToPlainStringontoTypeCoercionHelperand recurse into arrays when the expected type isAbstractArrayType/TypeCollection(for example,array_join).ImplicitTypeCastsfor bothImplicitCastInputTypesandExpectsInputTypes.implicitCastToStringforConcat/Eltinstead ofimplicitCast(e, StringType), which missed collated CHAR.StringHelper.transformingStringResultTypestripping; a result type now follows its promoted child.ExpectsInputTypesforStringSplitSQL.JsonTuplea 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
ExpectsInputTypessite (str_to_map,array_join, and others) and collated CHAR onConcat, 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 aCAST(... 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 unboundedSTRING. Result values are unchanged versus the previous per-expression stripping.How was this patch tested?
TypeCoercionSuite: legacy and ANSIConcat/Eltpromotion, includingCHAR(n) COLLATE UTF8_LCASE;ImplicitTypeCastspromotion; and unchangedJsonTupleINT / untyped NULL handling.BasicCharVarcharTestSuite: transforming string result types and collated Concat / Elt values.charvarchar-standard-semantics.sqlresult and analyzer goldens.Was this patch authored or co-authored using generative AI tooling?
Yes (Cursor).