cloud-fan commented on code in PR #58224:
URL: https://github.com/apache/spark/pull/58224#discussion_r4336710538
##########
docs/sql-ref-parameter-markers.md:
##########
@@ -29,6 +29,24 @@ You cannot mix named and unnamed parameter markers in the
same SQL statement.
Parameter markers can be used wherever literal values are allowed in the SQL
syntax.
+The marker syntax (`:name` or `?`) does not declare a type. The type is the
type of the
+bound value:
+
+* In SQL, `USING` binds a [constant
expression](sql-ref-syntax-aux-exec-imm.html).
+ A [string literal](sql-ref-literals.html#string-literal) such as `'ab'` is
`STRING`.
+ Use a typed literal (`5L`, `DATE '2024-01-01'`) or `CAST` to choose another
type.
+ CHAR and VARCHAR are the exception: a `USING` value such as `CAST('ab' AS
CHAR(2))`
Review Comment:
**Nit (P3):** This is true for a CHAR/VARCHAR value without an explicit
collation, but not for one that has one. `ParameterHandler.convertToSql`
substitutes plain `Literal.sql` (`'ab'`, which re-parses as STRING) only when
`hasNonDefaultStringCharOrVarcharType` is false. For `USING CAST('ab' AS
CHAR(2) COLLATE UTF8_LCASE) AS p`, or an explicit `COLLATE UTF8_BINARY`, it
substitutes `CAST('ab' AS CHAR(2) COLLATE UTF8_LCASE)` instead. So under the
preview, `TYPEOF(:p)` is `char(2) collate UTF8_LCASE`, not `string`. I traced
this from source and did not run it.
The same unqualified statement appears at line 198 of this page and in the
`arg_expr` description on the EXECUTE IMMEDIATE page. Limiting all three to
values without an explicit collation would make them accurate, for example "a
CHAR or VARCHAR value without an explicit collation currently binds as STRING".
The advice to cast the marker in the SQL text still applies.
**Verification:**
- **Behavior:** With the preview flag on, TYPEOF of a marker bound through
EXECUTE IMMEDIATE USING to a CHAR(2) value without explicit collation reports
string, and one bound to an explicitly collated CHAR(2) value reports the
collated char(2) type. The revised wording must describe both outcomes.
- **Inspection:** Each revised binding statement agrees with how parse-time
substitution renders CHAR/VARCHAR literals with and without an explicit
collation.
##########
docs/sql-ref-ansi-compliance.md:
##########
@@ -198,6 +202,14 @@ While casting of a decimal with a fraction to an interval
type with SECOND as th
### Store assignment
As mentioned at the beginning, when `spark.sql.storeAssignmentPolicy` is set
to `ANSI`(which is the default value), Spark SQL complies with the ANSI store
assignment rules on table insertions. The valid combinations of source and
target data type in table insertions are given by the following table.
+Table CHAR/VARCHAR columns apply length checks, trailing-space trimming, and
CHAR padding on
+insert in the default overlay. CHAR/VARCHAR that survive in query plans
require first-class
+CHAR/VARCHAR types (the preview flag or
`spark.sql.preserveCharVarcharTypeInfo`). Distinct
+CAST and assignment semantics require the preview flag: an explicit CAST
truncates or pads to
Review Comment:
**Nit (P3):** This holds only when the source is already a character string
type. `ToStringBase.castToString` uses the truncating
`charTypeCast`/`varcharTypeCast` only for a STRING/CHAR/VARCHAR source. Every
other source gets the same write-side check as assignment. So under the
preview, `CAST(12345 AS VARCHAR(4))` raises `EXCEED_LIMIT_LENGTH` and
`TRY_CAST(12345 AS VARCHAR(4))` returns NULL, as pinned in
`charvarchar-standard-semantics.sql.out`; neither one truncates to `'1234'`.
This paragraph sits above a table of numeric, datetime, boolean, and other
source types, so readers will take it to cover those too. It also disagrees
with the Casts section of the Character String Types page, which gets the split
right. Maybe: "an explicit CAST from a character string type truncates or pads
to the length; a CAST from any other type, like assignment, raises
`EXCEED_LIMIT_LENGTH` when the value does not fit".
**Verification:**
- **Inspection:** The revised paragraph separates casts from character
string types (truncate or pad) from casts of non-character values
(assignment-style length check that raises EXCEED_LIMIT_LENGTH, or NULL under
TRY_CAST). This must agree with the CAST(12345 AS VARCHAR(4)) and
TRY_CAST(12345 AS VARCHAR(4)) results under the preview flag.
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/avroSqlFunctions.scala:
##########
@@ -58,6 +58,16 @@ import org.apache.spark.util.Utils
is undefined: it may fail or return arbitrary result.
To deserialize the data with a compatible and evolved schema, the expected
Avro schema can be
set via the corresponding option.
+ CHAR/VARCHAR come from the spark.sql.catalyst.type property on Avro string
fields
Review Comment:
**Nit (P3):** This covers fields and values, but not the map keys the note
goes on to describe. Avro map keys have no schema of their own.
`SchemaConverters` therefore stamps a CHAR/VARCHAR key type as
`spark.sql.catalyst.mapKey.type` on the enclosing map schema and reads the key
type only from there (`SchemaConverters.scala:98-115` and `304-308`),
defaulting to STRING. A user writing a `jsonFormatSchema` from this note cannot
tell how to declare a CHAR key. Without that property, the map-key check
described below never applies.
Could this name the map-key property too? For example: "CHAR/VARCHAR come
from the spark.sql.catalyst.type property on Avro string fields, and for map
keys from spark.sql.catalyst.mapKey.type on the Avro map schema (both written
by Spark's Avro writer)". The PySpark `from_avro` docstring has the same
sentence.
This extends an [existing
discussion](https://github.com/apache/spark/pull/58224#discussion_r4330347828).
**Verification:**
- **Inspection:** The provenance text matches the property names the Avro
schema converter writes and reads for string fields and for map keys.
- **Inspection:** The edited from_avro note keeps a leading newline with
four-space indentation and a trailing newline with two-space indentation, so
built-in function registration accepts it.
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/jsonExpressions.scala:
##########
@@ -1926,6 +1926,14 @@ object JsonExistsExpressionBuilder extends
JsonPathExpressionBuilder {
> SELECT _FUNC_('{"teacher": "Alice", "student": [{"name": "Bob",
"rank": 1}, {"name": "Charlie", "rank": 2}]}', 'STRUCT<teacher: STRING,
student: ARRAY<STRUCT<name: STRING, rank: INT>>>');
{"teacher":"Alice","student":[{"name":"Bob","rank":1},{"name":"Charlie","rank":2}]}
""",
+ note = """
+ A CHAR `jsonStr` is not rtrimmed; padding is parsed as trailing whitespace
in the JSON
+ text. CHAR/VARCHAR in `schema` requires the preview flag
+ spark.sql.charVarchar.standardSemantics.enabled: fields are assigned (CHAR
padded;
+ overflow is a bad record) and map keys are length-checked without padding
or trimming.
+ Without the flag, CHAR/VARCHAR in `schema` raises
Review Comment:
Thanks, verified at the current head: the from_json/from_csv/from_xml notes
and docstrings now describe the three-state gate (first-class via either flag,
legacy as-string, otherwise UNSUPPORTED_CHAR_OR_VARCHAR_AS_STRING), map-key
checks stay on the preview flag, and the reference pages say "by default".
from_avro is correctly two-state now: its Avro schema never goes through
failIfHasCharVarchar, so my suggestion above to give it the three-state wording
was wrong.
<!-- SPARK_DEV_REVIEW_REPLY
{"feedback_id":"inline:4322260523","thread_id":"inline:4322260523","verdict_sha256":"685b9d18cefdcd97fe201d9358349bab2297d25842c2185c234667e6907a8e28"}
-->
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/avroSqlFunctions.scala:
##########
@@ -58,6 +58,10 @@ import org.apache.spark.util.Utils
is undefined: it may fail or return arbitrary result.
To deserialize the data with a compatible and evolved schema, the expected
Avro schema can be
set via the corresponding option.
+ CHAR/VARCHAR assignment and map-key checks require the preview flag
+ spark.sql.charVarchar.standardSemantics.enabled. Then STRING values are
assigned to
+ CHAR/VARCHAR (CHAR padded; overflow raises EXCEED_LIMIT_LENGTH). Avro ENUM
symbols are
Review Comment:
Thanks, verified: the from_avro note and the Parsed values paragraph now
describe overflow as a malformed record (FAILFAST with EXCEED_LIMIT_LENGTH as
the cause, PERMISSIVE as a struct with every field NULL), and the migration
bullet uses the same PERMISSIVE wording.
<!-- SPARK_DEV_REVIEW_REPLY
{"feedback_id":"inline:4322260534","thread_id":"inline:4322260534","verdict_sha256":"685b9d18cefdcd97fe201d9358349bab2297d25842c2185c234667e6907a8e28"}
-->
##########
docs/sql-migration-guide.md:
##########
@@ -29,9 +29,12 @@ license: |
## Upgrading from Spark SQL 4.3 to 4.4
- Since Spark 4.4, `spark.sql.execution.arrow.pythonUDF.columnarInput.enabled`
is `false` by default: Arrow-based Python UDFs read rows from an Arrow-backed
upstream operator, such as a Data Source V2 scan or the Arrow cache, as before
Spark 4.2. With columnar input, they kept the columns that they pass through
until the results of their batch came back from Python, and failed or returned
wrong results when the operator reused or released the vectors of the batch in
the meantime. To enable columnar input again, set
`spark.sql.execution.arrow.pythonUDF.columnarInput.enabled` to `true`.
-- Since Spark 4.4, when `spark.sql.charVarchar.standardSemantics.enabled` is
true, `from_json` length-checks JSON object names used as `MAP<CHAR(n), _>` or
`MAP<VARCHAR(n), _>` keys without padding or trimming them. A `CHAR(n)` key
must already be exactly `n` characters, and a `VARCHAR(n)` key must already be
at most `n` characters; duplicate names are kept, exactly as for STRING keys,
and `spark.sql.mapKeyDedupPolicy` is not applied. A key that fails the check
makes the enclosing map a bad record: in `PERMISSIVE` mode the nearest
enclosing struct field becomes `null` (its sibling fields are preserved), and
if the map is inside an array or another map, that whole array or map becomes
`null`; in `FAILFAST` mode parsing fails with
`UNSUPPORTED_JSON_CHAR_VARCHAR_MAP_KEY` (SQLSTATE `0A000`) as the cause of
`MALFORMED_RECORD_IN_PARSING`. The same parser-level check runs for any JSON
scan whose reader schema carries a CHAR/VARCHAR map key, including catalog JSON
tables, since under standa
rd semantics those keys stay CHAR/VARCHAR all the way to the scan: an
off-width key becomes a bad record there too, rather than being padded or
raising `EXCEED_LIMIT_LENGTH`. Unlike `from_json`, keys that pass the check are
then rebuilt read-side, so on the scan path `spark.sql.mapKeyDedupPolicy` does
apply and a duplicate name can raise `DUPLICATED_MAP_KEY`. JSON file scans,
including scans inside persisted views, read the flag from the executing
session.
+- Since Spark 4.4, `spark.sql.charVarchar.standardSemantics.enabled` (default
`false`) is a preview feature that may change in future releases. When true, it
enables first-class `CHAR(n)` and `VARCHAR(n)` semantics throughout schemas and
query plans: `CHAR` -> `VARCHAR` -> `STRING` form a type-precedence family,
explicit casts can return constrained types, least-common-type resolution for
`COALESCE`/`CASE`/`UNION` can return `CHAR` or `VARCHAR`, and string
expressions that transform their input return `STRING`. Assignment and reads
enforce the declared length, padding `CHAR` to `n` characters and raising
`EXCEED_LIMIT_LENGTH` when non-space content does not fit. The default remains
the Spark 3.1 table-schema overlay on `STRING`. This opt-in mode supersedes the
experimental `spark.sql.preserveCharVarcharTypeInfo`, which instead keeps
constrained types through transforming expressions. See [Character String
Types](sql-ref-character-string-types.html) for the preview semantics; the ent
ries below note area-specific behavior that changed. Those later CHAR/VARCHAR
bullets apply only when this preview flag is true, except where they name
`preserveCharVarcharTypeInfo` alone.
+- Since Spark 4.4, when `spark.sql.charVarchar.standardSemantics.enabled` is
true, `from_json` length-checks JSON object names used as `MAP<CHAR(n), _>` or
`MAP<VARCHAR(n), _>` keys without padding or trimming them. A `CHAR(n)` key
must already be exactly `n` characters, and a `VARCHAR(n)` key must already be
at most `n` characters; duplicate names are kept, exactly as for STRING keys,
and `spark.sql.mapKeyDedupPolicy` is not applied. A key that fails the check
makes the enclosing map a bad record: in `PERMISSIVE` mode the nearest
enclosing struct field becomes `null` (its sibling fields are preserved), and
if the map is inside an array or another map, that whole array or map becomes
`null`; in `FAILFAST` mode parsing fails with
`UNSUPPORTED_JSON_CHAR_VARCHAR_MAP_KEY` (SQLSTATE `0A000`) as the cause of
`MALFORMED_RECORD_IN_PARSING`. The same parser-level check runs for any JSON
scan whose reader schema carries a CHAR/VARCHAR map key, including catalog JSON
tables, since under standa
rd semantics those keys stay CHAR/VARCHAR all the way to the scan: an
off-width key becomes a bad record there too, rather than being padded or
raising `EXCEED_LIMIT_LENGTH`. Unlike `from_json`, keys that pass the check are
then rebuilt read-side, so on the scan path `spark.sql.mapKeyDedupPolicy` does
apply and a duplicate name can raise `DUPLICATED_MAP_KEY`. JSON file scans,
including scans inside persisted views, read the flag from the executing
session. The optimizer no longer folds `from_json(to_json(m))` to `m` when that
schema has a CHAR/VARCHAR map key, so the length check still runs on the round
trip.
+- Since Spark 4.4, `from_json`, `from_csv`, `from_xml`, and the corresponding
`schema_of_*` functions do not rtrim a CHAR document argument. CHAR padding is
part of the STRING value after CAST, so it is parsed as trailing spaces in the
document. JSON and XML tolerate that trailing whitespace; CSV can tokenize it
into extra fields or attach it to the last unquoted field (use
`ignoreTrailingWhiteSpace` to restore a trimmed last-field parse).
- Since Spark 4.4, when `spark.sql.preserveCharVarcharTypeInfo` is true and
`spark.sql.charVarchar.standardSemantics.enabled` is false, ORC reads that
apply a CHAR/VARCHAR schema over STRING storage return the stored values
without ORC truncation, matching Parquet. Previously the ORC reader requested
`char(n)`/`varchar(n)` and truncated STRING-stored values to `n`. Read-side
length checks (`EXCEED_LIMIT_LENGTH`) apply only when
`spark.sql.charVarchar.standardSemantics.enabled` is true.
- Since Spark 4.4, when `spark.sql.charVarchar.standardSemantics.enabled` is
true, XML names used as `MAP<CHAR(n), _>` or `MAP<VARCHAR(n), _>` keys in
`from_xml` and the XML datasource are length-checked without padding or
trimming. The checked names are element names, `attributePrefix` plus attribute
names (default prefix `_`), and the `valueTag` (default `_VALUE`) when mixed
text is present. A `CHAR(n)` key must already be exactly `n` characters, and a
`VARCHAR(n)` key must already be at most `n` characters. Mismatched keys fail
with `UNSUPPORTED_XML_CHAR_VARCHAR_MAP_KEY` (SQLSTATE `0A000`) and follow the
XML parse mode (`PERMISSIVE` or `FAILFAST`). Exact repeated names last-win;
`spark.sql.mapKeyDedupPolicy` is not applied. XML CHAR/VARCHAR values use the
same pad and `EXCEED_LIMIT_LENGTH` checks as other parsed text. Empty,
attribute-only, and text-only map elements stay SQL NULL or a malformed record;
they do not become an empty map or a `valueTag` entry. In Spark 4.3, `from_xm
l` and XML reader schemas rejected CHAR/VARCHAR with
`UNSUPPORTED_CHAR_OR_VARCHAR_AS_STRING`, or replaced them with STRING when
`spark.sql.legacy.charVarcharAsString` was true, so CHAR/VARCHAR map keys did
not reach the parser. When the standard-semantics flag is false and
`spark.sql.preserveCharVarcharTypeInfo` is true, short CHAR keys are padded and
over-length CHAR or VARCHAR keys raise `EXCEED_LIMIT_LENGTH`.
+- Since Spark 4.4, when `spark.sql.charVarchar.standardSemantics.enabled` is
true, `from_avro` assigns CHAR/VARCHAR on STRING values (pad, then
trim-and-`EXCEED_LIMIT_LENGTH`; Avro ENUM symbols are not assigned). Avro map
keys of CHAR/VARCHAR are length-checked without padding or trimming in
`from_avro` and Avro file scans, matching JSON/XML object names; an off-width
key fails with `UNSUPPORTED_AVRO_CHAR_VARCHAR_MAP_KEY` (SQLSTATE `0A000`).
`from_avro` PERMISSIVE nulls that malformed record; file scans do not wrap
per-row PERMISSIVE around the error. `to_avro` does not pad or trim. Avro file
scans still leave CHAR padding of STRING values to `ApplyCharTypePadding`.
Review Comment:
Thanks, verified: the migration bullet now uses the user-facing term
"read-side CHAR padding" instead of the internal rule name.
<!-- SPARK_DEV_REVIEW_REPLY
{"feedback_id":"inline:4322260540","thread_id":"inline:4322260540","verdict_sha256":"685b9d18cefdcd97fe201d9358349bab2297d25842c2185c234667e6907a8e28"}
-->
##########
docs/sql-ref-parameter-markers.md:
##########
@@ -29,6 +29,21 @@ You cannot mix named and unnamed parameter markers in the
same SQL statement.
Parameter markers can be used wherever literal values are allowed in the SQL
syntax.
+The marker syntax (`:name` or `?`) does not declare a type. The type is the
type of the
+bound value:
+
+* In SQL, `USING` binds a [constant
expression](sql-ref-syntax-aux-exec-imm.html).
+ A [string literal](sql-ref-literals.html#string-literal) such as `'ab'` is
`STRING`.
+ Use a typed literal (`5L`, `DATE '2024-01-01'`) or `CAST` to choose another
type.
+* In Python, Scala, or Java `spark.sql(..., args)`, Spark infers the type from
the host
+ value. A Python or Java `str` / `String` is `STRING`. You may pass a
constant `Column`,
+ including `lit('ab').cast('CHAR(2)')`.
Review Comment:
Thanks, verified: the page now limits host spark.sql args to literal-like
constants, calls out lit(...).cast(...) as INVALID_SQL_ARG, and the Python
example casts the marker in the SQL text. I was also wrong above that the
EXECUTE IMMEDIATE ... USING CAST(...) examples were fine: parameter
substitution renders a default-collation CHAR/VARCHAR value back as a plain
string literal, so it binds as STRING, and removing those examples was right.
<!-- SPARK_DEV_REVIEW_REPLY
{"feedback_id":"inline:4322260542","thread_id":"inline:4322260542","verdict_sha256":"685b9d18cefdcd97fe201d9358349bab2297d25842c2185c234667e6907a8e28"}
-->
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/csvExpressions.scala:
##########
@@ -152,6 +160,10 @@ case class CsvToStructs(
> SELECT _FUNC_('1,abc');
STRUCT<_c0: INT, _c1: STRING>
""",
+ note = """
+ A CHAR `csv` argument is not rtrimmed. Padding can change the inferred
field count and
+ types; for example CAST('1' AS CHAR(3)) infers DOUBLE with the default
comma delimiter.
Review Comment:
Thanks, verified: the schema_of_csv note now qualifies the DOUBLE inference
to first-class CHAR, and the family page states the default-mode INT result.
<!-- SPARK_DEV_REVIEW_REPLY
{"feedback_id":"inline:4322260550","thread_id":"inline:4322260550","verdict_sha256":"685b9d18cefdcd97fe201d9358349bab2297d25842c2185c234667e6907a8e28"}
-->
--
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]