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


##########
sql/api/src/main/scala/org/apache/spark/sql/types/CharType.scala:
##########
@@ -23,6 +23,10 @@ import org.apache.spark.sql.catalyst.util.CollationFactory
 /**
  * A data type representing fixed-length character strings with a specified 
length.
  *
+ * A standalone collated `CharType` writes its collation inline in JSON and 
therefore requires a
+ * current reader. Within a [[StructField]], schema JSON stores the collation 
in field metadata
+ * and emits an uncollated `char(n)` type so preceding readers can still read 
the schema.

Review Comment:
   **Nit (P3):** `preceding readers` sounds like reader or parser order, but 
this compatibility boundary is about Spark versions. Could the parallel 
CharType and VarcharType notes say `older readers` or `readers from earlier 
Spark versions` so users know which readers preserve the collation?



##########
sql/api/src/main/scala/org/apache/spark/sql/types/DataType.scala:
##########
@@ -469,24 +539,49 @@ object DataType {
   /**
    * Returns a map of field path to collation name.
    */
-  private def getCollationsMap(metadataFields: List[JField]): Map[String, 
String] = {
-    val collationsJsonOpt = metadataFields.find(_._1 == 
COLLATIONS_METADATA_KEY).map(_._2)
-    collationsJsonOpt match {
+  private def getCollationsMap(
+      metadataFields: List[JField],
+      metadataKey: String): Map[String, String] = {
+    val requireCompleteMap = metadataKey == 
CHAR_VARCHAR_COLLATIONS_METADATA_KEY
+    metadataFields.find(_._1 == metadataKey).map(_._2) match {
       case Some(JObject(fields)) =>
-        fields.collect { case (fieldPath, JString(collation)) =>
-          collation.split("\\.", 2) match {
-            case Array(provider: String, collationName: String) =>
-              CollationFactory.assertValidProvider(provider)
-              fieldPath -> collationName
-          }
-        }.toMap
-
+        val parsed = Map.newBuilder[String, String]

Review Comment:
   **Non-blocking (P2):** The parser validates each restoration value but never 
verifies that every path was used. For a field `c: char(4)`, metadata 
containing `"typo": "spark.UTF8_LCASE"` matches no traversal point, yet the 
reader removes the reserved key and returns an uncollated `CharType(4)`. Please 
reject any leftover path before stripping the metadata in both Scala and Python 
so a typo cannot silently change the schema.
   
   See **Shared repair plan 1** in the review body.



##########
python/pyspark/sql/types.py:
##########
@@ -2674,6 +2765,41 @@ def _parse_datatype_json_value(  # type: ignore[return]
             )
 
 
+def _parse_collation_metadata_map(metadata: Optional[Dict[str, Any]], key: 
str) -> Dict[str, str]:
+    if not metadata or key not in metadata:
+        return {}
+
+    raw = metadata[key]
+    if key == _CHAR_VARCHAR_COLLATIONS_METADATA_KEY:
+        if not isinstance(raw, dict):
+            raise _invalid_char_varchar_collation_metadata(raw)
+        parsed: Dict[str, str] = {}
+        for path, value in raw.items():
+            name_parts = value.split(".") if isinstance(value, str) else None
+            if name_parts is None or len(name_parts) != 2:

Review Comment:
   **Non-blocking (P2):** `spark.` passes the two-element split check here, so 
Python constructs `CharType(4, '')` and reserializes it as `icu.`; Scala 
rejects the same schema. Please require both the provider and collation-name 
components to be non-empty before accepting the restoration entry.
   
   See **Shared repair plan 1** in the review body.



##########
sql/api/src/main/scala/org/apache/spark/sql/types/DataType.scala:
##########
@@ -425,12 +468,16 @@ object DataType {
           ("name", JString(name)),
           ("nullable", JBool(nullable)),
           ("type", dataType: JValue)) =>
-      val collationsMap = getCollationsMap(metadataFields)
-      val metadataWithoutCollations =
-        JObject(metadataFields.filterNot(_._1 == COLLATIONS_METADATA_KEY))
+      val collationsMap = getCollationsMap(metadataFields, 
COLLATIONS_METADATA_KEY)
+      val charVarcharCollationsMap =
+        getCollationsMap(metadataFields, CHAR_VARCHAR_COLLATIONS_METADATA_KEY)

Review Comment:
   Confirmed on the current head: both Scala UDT object branches validate the 
outer __CHAR_VARCHAR_COLLATIONS target before returning, without traversing 
UDT.sqlType.
   
   <!-- SPARK_DEV_REVIEW_REPLY 
{"feedback_id":"inline:4040154030","thread_id":"inline:4040154030","verdict_sha256":"b7c8c20b59a27a62b38100b999aea0571fb95ab54bd57ec94b5fa8d7aafe6730"}
 -->



##########
python/pyspark/sql/types.py:
##########
@@ -2429,8 +2493,8 @@ def fromWKB(cls, wkb: bytes, srid: int) -> "Geometry":
     "interval": CalendarIntervalType,
 }
 
-_LENGTH_CHAR = re.compile(r"char\(\s*(\d+)\s*\)")
-_LENGTH_VARCHAR = re.compile(r"varchar\(\s*(\d+)\s*\)")
+_LENGTH_CHAR = re.compile(r"char\(\s*(\d+)\s*\)(?:\s+collate\s+(\w+))?")

Review Comment:
   Confirmed on the current head: standalone Python CHAR/VARCHAR JSON parsing 
uses full matches and rejects trailing tokens.
   
   <!-- SPARK_DEV_REVIEW_REPLY 
{"feedback_id":"inline:4040154037","thread_id":"inline:4040154037","verdict_sha256":"b7c8c20b59a27a62b38100b999aea0571fb95ab54bd57ec94b5fa8d7aafe6730"}
 -->



##########
python/pyspark/sql/types.py:
##########
@@ -324,47 +324,82 @@ def isUTF8BinaryCollation(self) -> bool:
 
 
 class CharType(AtomicType):
-    """Char data type
+    """Char data type.
+
+    A standalone collated ``CharType`` writes its collation inline in JSON and 
therefore requires

Review Comment:
   Confirmed on the current head: the CharType/VarcharType docs now explain 
that older Python readers may accept only the constrained-type prefix and lose 
the inline collation.
   
   <!-- SPARK_DEV_REVIEW_REPLY 
{"feedback_id":"inline:4040154040","thread_id":"inline:4040154040","verdict_sha256":"b7c8c20b59a27a62b38100b999aea0571fb95ab54bd57ec94b5fa8d7aafe6730"}
 -->



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