ulysses-you commented on code in PR #60078:
URL: https://github.com/apache/spark/pull/60078#discussion_r4465669186


##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/physical/partitioning.scala:
##########
@@ -750,15 +750,30 @@ case class KeyedPartitioning(
   /**
    * Prints the layout's contents where the value object would print, which 
keeps the plan string as
    * it was before the layout held them. The list is curated rather than the 
layout's own fields,
-   * for two reasons. `partitionKeys` has to be printed as a `Seq` for 
`maxFields` to truncate it,
-   * and a partitioning can hold one key per split. And `dataTypes` has its 
naming erased, so
-   * printing it would put a struct field name into a plan that appears 
nowhere in the query. A
-   * field the layout grows is therefore a decision here, and the default is 
to print it: the plan
-   * string is the only place a reader sees why a marked partitioning still 
shuffles.
+   * for two reasons. A partitioning can hold one key per split, so 
`partitionKeys` is summarized:
+   * as many keys as `spark.sql.debug.maxToStringFields` has room for, then 
their total. And
+   * `dataTypes` has its naming erased, so printing it would put a struct 
field name into a plan
+   * that appears nowhere in the query. A field the layout grows is therefore 
a decision here, and
+   * the default is to print it: the plan string is the only place a reader 
sees why a marked
+   * partitioning still shuffles.
    */
-  override protected def stringArgs: Iterator[Any] =
-    Iterator(expressions, partitionKeys, isGrouped, isCollapsed) ++ 
unknownKeysOrigin ++
-      ungroupingOrigin
+  override protected def stringArgs: Iterator[Any] = {
+    // `Expression.toString` truncates the flattened argument list at 
`maxToStringFields`. The
+    // expressions and the fields printed after the keys keep their slots, and 
the keys take what
+    // is left; when they cannot all fit, their total takes one key slot. When 
there is not even a
+    // slot left for that, the total still wins over a trailing field, which 
the outer truncation
+    // then drops.
+    val trailing = Seq[Any](isGrouped, isCollapsed) ++ unknownKeysOrigin ++ 
ungroupingOrigin
+    val keyBudget = math.max(0, SQLConf.get.maxToStringFields - 
expressions.size - trailing.size)

Review Comment:
   Took the second alternative: toString/simpleString are overridden, and both 
the key list and the argument list are rendered with truncatedString. There is 
no manual flattening of the argument list.



##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/physical/partitioning.scala:
##########
@@ -750,15 +750,30 @@ case class KeyedPartitioning(
   /**
    * Prints the layout's contents where the value object would print, which 
keeps the plan string as
    * it was before the layout held them. The list is curated rather than the 
layout's own fields,
-   * for two reasons. `partitionKeys` has to be printed as a `Seq` for 
`maxFields` to truncate it,
-   * and a partitioning can hold one key per split. And `dataTypes` has its 
naming erased, so
-   * printing it would put a struct field name into a plan that appears 
nowhere in the query. A
-   * field the layout grows is therefore a decision here, and the default is 
to print it: the plan
-   * string is the only place a reader sees why a marked partitioning still 
shuffles.
+   * for two reasons. A partitioning can hold one key per split, so 
`partitionKeys` is summarized:
+   * as many keys as `spark.sql.debug.maxToStringFields` has room for, then 
their total. And
+   * `dataTypes` has its naming erased, so printing it would put a struct 
field name into a plan
+   * that appears nowhere in the query. A field the layout grows is therefore 
a decision here, and
+   * the default is to print it: the plan string is the only place a reader 
sees why a marked
+   * partitioning still shuffles.
    */
-  override protected def stringArgs: Iterator[Any] =
-    Iterator(expressions, partitionKeys, isGrouped, isCollapsed) ++ 
unknownKeysOrigin ++
-      ungroupingOrigin
+  override protected def stringArgs: Iterator[Any] = {
+    // `Expression.toString` truncates the flattened argument list at 
`maxToStringFields`. The
+    // expressions and the fields printed after the keys keep their slots, and 
the keys take what
+    // is left; when they cannot all fit, their total takes one key slot. When 
there is not even a
+    // slot left for that, the total still wins over a trailing field, which 
the outer truncation
+    // then drops.
+    val trailing = Seq[Any](isGrouped, isCollapsed) ++ unknownKeysOrigin ++ 
ungroupingOrigin
+    val keyBudget = math.max(0, SQLConf.get.maxToStringFields - 
expressions.size - trailing.size)
+    val keys: Seq[Any] =
+      if (partitionKeys.length <= keyBudget) {

Review Comment:
   Guarded: a deserialized partitioning prints null. Covered by a 
JavaSerializer round-trip test.



##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/physical/partitioning.scala:
##########
@@ -750,15 +750,30 @@ case class KeyedPartitioning(
   /**
    * Prints the layout's contents where the value object would print, which 
keeps the plan string as
    * it was before the layout held them. The list is curated rather than the 
layout's own fields,
-   * for two reasons. `partitionKeys` has to be printed as a `Seq` for 
`maxFields` to truncate it,
-   * and a partitioning can hold one key per split. And `dataTypes` has its 
naming erased, so
-   * printing it would put a struct field name into a plan that appears 
nowhere in the query. A
-   * field the layout grows is therefore a decision here, and the default is 
to print it: the plan
-   * string is the only place a reader sees why a marked partitioning still 
shuffles.
+   * for two reasons. A partitioning can hold one key per split, so 
`partitionKeys` is summarized:
+   * as many keys as `spark.sql.debug.maxToStringFields` has room for, then 
their total. And
+   * `dataTypes` has its naming erased, so printing it would put a struct 
field name into a plan
+   * that appears nowhere in the query. A field the layout grows is therefore 
a decision here, and
+   * the default is to print it: the plan string is the only place a reader 
sees why a marked
+   * partitioning still shuffles.
    */
-  override protected def stringArgs: Iterator[Any] =
-    Iterator(expressions, partitionKeys, isGrouped, isCollapsed) ++ 
unknownKeysOrigin ++
-      ungroupingOrigin
+  override protected def stringArgs: Iterator[Any] = {
+    // `Expression.toString` truncates the flattened argument list at 
`maxToStringFields`. The
+    // expressions and the fields printed after the keys keep their slots, and 
the keys take what
+    // is left; when they cannot all fit, their total takes one key slot. When 
there is not even a
+    // slot left for that, the total still wins over a trailing field, which 
the outer truncation
+    // then drops.
+    val trailing = Seq[Any](isGrouped, isCollapsed) ++ unknownKeysOrigin ++ 
ungroupingOrigin
+    val keyBudget = math.max(0, SQLConf.get.maxToStringFields - 
expressions.size - trailing.size)
+    val keys: Seq[Any] =
+      if (partitionKeys.length <= keyBudget) {
+        partitionKeys
+      } else {
+        partitionKeys.take(math.max(0, keyBudget - 1)) :+

Review Comment:
   The summary now goes through truncatedString, so the take/math.max 
arithmetic is gone.



##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/plans/physical/partitioning.scala:
##########
@@ -750,15 +750,30 @@ case class KeyedPartitioning(
   /**
    * Prints the layout's contents where the value object would print, which 
keeps the plan string as

Review Comment:
   Rewritten to describe the current fields and the collation caveat.



##########
sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/DistributionSuite.scala:
##########
@@ -508,6 +508,49 @@ class DistributionSuite extends SparkFunSuite with 
SQLHelper {
     assert(interned.layout eq kpX.layout, "the whole layout is interned, keys 
and all")
   }
 
+  test("SPARK-60859: a KeyedPartitioning plan string shows the keys that fit 
and their total") {
+    val x = AttributeReference("x", IntegerType)()
+    val keys = (1 to 12).map(InternalRow(_))
+
+    // One expression, the total and the two flags take four of the ten slots.
+    withSQLConf(SQLConf.MAX_TO_STRING_FIELDS.key -> "10") {
+      assert(KeyedPartitioning(Seq(x), keys).toString ==
+        s"keyedpartitioning($x, [1], [2], [3], [4], [5], [6], " +
+          "... 12 partition keys in total, true, false)")
+
+      // When every key fits, the total is not needed.
+      assert(KeyedPartitioning(Seq(x), keys.take(6)).toString ==

Review Comment:
   Added: exact fit (7 keys at maxFields = 10), 8 keys (truncated), an empty 
key list, and the deserialized null case.



##########
sql/catalyst/src/test/scala/org/apache/spark/sql/catalyst/util/InternalRowComparableWrapperSuite.scala:
##########
@@ -153,4 +154,24 @@ class InternalRowComparableWrapperSuite extends 
SparkFunSuite {
     assert(wrap(IntegerType, InternalRow(1)) !== wrap(LongType, 
InternalRow(1L)),
       "and a type that decides where a value belongs still tells them apart")
   }
+
+  test("SPARK-60859: a wrapper rejects a row whose width disagrees with its 
types") {
+    val factory = InternalRowComparableWrapper
+      .getInternalRowComparableWrapperFactory(Seq(IntegerType, IntegerType))
+    val e = intercept[IllegalArgumentException] {
+      factory(InternalRow(1, 2, 3))
+    }
+    assert(e.getMessage.contains("The row has 3 fields, but the types it is 
compared at have 2"))
+  }
+
+  test("SPARK-60859: a wrapper prints its row values") {
+    val factory = InternalRowComparableWrapper
+      .getInternalRowComparableWrapperFactory(Seq(IntegerType, StringType))
+    assert(factory(InternalRow(1, UTF8String.fromString("a"))).toString === 
"[1,a]")
+
+    // An UnsafeRow has no toString of its own, so the wrapper has to read the 
row.

Review Comment:
   Fixed: UnsafeRow's own toString is a hex dump of its words; the test comment 
now says so.



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