srielau commented on code in PR #58132:
URL: https://github.com/apache/spark/pull/58132#discussion_r3817597423


##########
sql/connect/common/src/main/scala/org/apache/spark/sql/connect/client/SparkResult.scala:
##########
@@ -101,7 +101,7 @@ private[sql] class SparkResult[T](
       case UnboundRowEncoder =>
         // Replace the row encoder with the encoder inferred from the schema.
         RowEncoder
-          .encoderFor(dataType.asInstanceOf[StructType])
+          .encoderForResultSchema(dataType.asInstanceOf[StructType])

Review Comment:
   Correct call site: only `UnboundRowEncoder` (row `collect`) should ignore 
local CHAR/VARCHAR config. Typed encoders stay as-is and still decode Arrow 
string vectors via `StringEncoder`.
   
   Worth a sentence in the PR description: without this, the JDBC CAST collect 
assertions in this PR would fail on a client that only has `DefaultSqlApiConf`.



##########
sql/api/src/main/scala/org/apache/spark/sql/catalyst/encoders/RowEncoder.scala:
##########
@@ -72,14 +72,31 @@ object RowEncoder extends DataTypeErrorsBase {
     encoderForDataType(schema, lenient).asInstanceOf[AgnosticEncoder[Row]]
   }
 
+  /**
+   * Builds an encoder for a schema that the engine produced, such as the 
result schema of a Spark
+   * Connect query. Whether CHAR/VARCHAR are first class types is decided by 
the session that
+   * produced the schema, so they are always accepted here. A client cannot 
read that session's
+   * configuration, and refusing the type would make the result undecodable.
+   */
+  private[sql] def encoderForResultSchema(schema: StructType): 
AgnosticEncoder[Row] =

Review Comment:
   This is the right verb: engine-produced schema vs user-supplied schema, 
instead of reading a session conf the client does not have.
   
   Please add a negative test that `encoderFor` (not this method) still rejects 
`CharType` / `VarcharType` when `charVarcharFirstClassTypes` is false, so UDF / 
`createDataFrame` cannot pick up the result-schema override.



##########
sql/connect/common/src/main/scala/org/apache/spark/sql/connect/client/arrow/ArrowDeserializer.scala:
##########
@@ -135,8 +135,14 @@ object ArrowDeserializers {
         new Deserializer[Any] {
           def get(i: Int): Any = null
         }
-      case (StringEncoder, v: FieldVector) =>
-        new LeafFieldDeserializer[String](encoder, v, timeZoneId) {
+      // CHAR/VARCHAR travel as plain Arrow string vectors; the length is part 
of the type, not of
+      // the encoding, and the values arrive already padded and length checked 
by the server. Read
+      // them against the unconstrained string type, since narrowing STRING to 
CHAR(n)/VARCHAR(n)
+      // is not an up-cast and the reader would reject the vector.
+      case (StringEncoder | _: CharEncoder | _: VarcharEncoder, v: 
FieldVector) =>

Review Comment:
   Reading CHAR/VARCHAR through `plainStringType` is the right explanation 
(Arrow has no CHAR vector; narrowing is not an up-cast). Please keep the 
comment; it is load-bearing.
   
   The serializer change is needed for the ArrowEncoderSuite round trip. 
Production `SparkResult` is decode-only, so skipping write-side length checks 
here is acceptable -- just do not start using `encoderForResultSchema` to 
encode user input.



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