cloud-fan commented on code in PR #58581:
URL: https://github.com/apache/spark/pull/58581#discussion_r3992292084


##########
sql/connect/common/src/main/scala/org/apache/spark/sql/connect/SparkSession.scala:
##########
@@ -223,7 +223,10 @@ class SparkSession private[sql] (
 
   /** @inheritdoc */
   def createDataFrame(rows: java.util.List[Row], schema: StructType): 
DataFrame = {
-    createDataset(RowEncoder.encoderFor(schema), 
rows.iterator().asScala).toDF()
+    // RowEncoder consults the client process's local SqlApiConf, which can 
differ from the
+    // server-side configuration visible through SparkSession.conf. Encode an 
explicitly provided
+    // schema independently of that local configuration, as for result schemas.
+    createDataset(RowEncoder.encoderForResultSchema(schema), 
rows.iterator().asScala).toDF()

Review Comment:
   **Blocking (P1):** This path still bypasses 
`spark.sql.legacy.charVarcharAsString=true`. `encoderForResultSchema` pads CHAR 
values before the server policy is known, and `buildLocalRelationFromRows` 
later restores the original CHAR/VARCHAR attributes even when `Dataset.to` 
normalized the target to STRING. For example, `CharType(4)` with `"ab"` can 
come back as CHAR with `"ab  "` instead of STRING with `"ab"`. Could we keep 
the physical row values unpadded until server-side reconciliation and cover 
legacy, standard, and default policies for both empty and populated input?
   
   **Recommended change:** Separate the physical row encoder/schema from the 
requested logical schema for caller-provided Row data. Serialize CHAR/VARCHAR 
leaves as collation-preserving physical STRING values without length checks, 
carry the original schema separately, and have buildLocalRelationFromRows 
construct its final attributes from the server-policy-normalized target rather 
than unconditionally from the original schema.
   
   **Why this works:** Keeping raw strings on the wire prevents irreversible 
client-side CHAR padding. Server-side Dataset.to reconciliation can then apply 
default rejection, standard-semantics preservation, or legacy-as-string 
normalization once, and the resulting relation schema can match that selected 
policy.
   
   **Scope:** sql/connect/common, sql/connect/server, sql/connect/client/jvm
   
   **Compatibility:** Result-schema decoding may continue to use first-class 
CHAR/VARCHAR encoders; the separation applies to caller-provided local data 
whose legality is owned by the server session.
   
   **Risks:** Changing the shared local-relation builder could affect 
non-CHAR/VARCHAR caller schemas or chunked serialization. The physical STRING 
type must retain the requested collation so Arrow conversion does not alter 
string interpretation before reconciliation.
   
   **Constraints:** Do not use the client's local SqlApiConf to decide the 
server's CHAR/VARCHAR policy. Preserve standard-semantics padding and length 
checks, default-policy rejection, collation, nullability, and nested schema 
behavior. Keep empty and populated local relations on the same server 
reconciliation path.
   
   **Success:** With legacy charVarcharAsString enabled and standard semantics 
disabled, explicit CHAR/VARCHAR input yields STRING schema and unpadded values. 
With standard semantics enabled, explicit CHAR/VARCHAR input retains its 
logical schema and existing padding and length behavior. With neither mode 
enabled, empty and populated explicit CHAR/VARCHAR input fail with the same 
unsupported-type error. Nested CHAR/VARCHAR leaves and collations follow the 
same policy matrix.



##########
python/pyspark/sql/types.py:
##########
@@ -330,19 +330,31 @@ class CharType(AtomicType):
     ----------
     length : int
         the length limitation.
+    collation : str, optional

Review Comment:
   **Nit (P3):** Could this parameter documentation state that the default is 
`None`, meaning no explicitly declared collation, and distinguish it from 
explicit `UTF8_BINARY`? The latter is semantically binary but emits restoration 
metadata, while omission does not. The same clarification is needed for 
`VarcharType`.



##########
python/pyspark/sql/types.py:
##########
@@ -2671,7 +2708,12 @@ def _parse_datatype_json_value(  # type: ignore[return]
 def _assert_valid_type_for_collation(
     fieldPath: str, fieldType: Any, collationMap: Dict[str, str]
 ) -> None:
-    if fieldPath in collationMap and fieldType != "string":
+    is_string_type = (
+        fieldType == "string"
+        or (isinstance(fieldType, str) and _LENGTH_CHAR.fullmatch(fieldType) 
is not None)

Review Comment:
   **Blocking (P1):** This validation also accepts an already-collated 
constrained type when `__COLLATIONS` supplies the same field. For example, 
inline `char(4) collate UTF8_LCASE` plus metadata `spark.UTF8_BINARY` silently 
returns `CharType(4, 'UTF8_BINARY')`, discarding the inline declaration; the 
JVM rejects the dual encoding. Could we require uncollated 
`char(n)`/`varchar(n)` text whenever restoration metadata is present and add 
matching/conflicting negative cases?



##########
python/pyspark/sql/types.py:
##########
@@ -330,19 +330,31 @@ class CharType(AtomicType):
     ----------
     length : int
         the length limitation.
+    collation : str, optional
+        name of the collation.
     """
 
-    def __init__(self, length: int):
+    def __init__(self, length: int, collation: Optional[str] = None):
         self.length = length
+        self.collation = collation
 
     def simpleString(self) -> str:
-        return "char(%d)" % (self.length)
+        if self.collation is None:
+            return "char(%d)" % (self.length)
+
+        return "char(%d) collate %s" % (self.length, self.collation)
 
     def jsonValue(self) -> str:
-        return "char(%d)" % (self.length)
+        return self.simpleString()
 
     def __repr__(self) -> str:

Review Comment:
   **Nit (P3):** Please add collated `CharType` and `VarcharType` instances to 
the existing `eval(repr(instance)) == instance` coverage, including explicit 
`UTF8_BINARY` and a non-binary collation. The current implementation 
round-trips, but neither new conditional repr branch has a durable regression 
signal.



-- 
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]

Reply via email to