zifeif2 commented on code in PR #59409:
URL: https://github.com/apache/spark/pull/59409#discussion_r4266343822


##########
sql/connect/common/src/main/scala/org/apache/spark/sql/connect/client/GrpcExceptionConverter.scala:
##########
@@ -406,7 +406,170 @@ private[client] object GrpcExceptionConverter {
         errorClass = Option(getErrorClassOrFallback(params)),
         messageParameters = errorParamsToMessageParameters(params),
         context = params.queryContext,
-        sqlState = getSqlStateOrFallback(params))))
+        sqlState = getSqlStateOrFallback(params)))).map { case (name, 
constructor) =>
+    name -> { (params: ErrorParams) =>
+      if (params.errorClass.exists(ec => 
!SparkThrowableHelper.isValidErrorClass(ec))) {
+        // Connector conditions and conditions from newer servers may not be 
defined locally.
+        // Use the server message verbatim: parameters in ErrorInfo may have 
been truncated.
+        val exception = serverErrorFactory(name)(resolveParams(params))
+        if (params.cause.isDefined && exception.getCause == null) {
+          exception.initCause(params.cause.get)
+        }
+        exception
+      } else {
+        constructor(params)
+      }
+    }
+  }
+
+  // Constructors that accept an already rendered message without consulting 
the local registry.
+  private lazy val serverErrorFactory: Map[String, ErrorParams => Throwable] = 
Map(
+    errorConstructor(params =>
+      new StreamingQueryException(
+        queryDebugString = "", // Already included in the server message.

Review Comment:
   [P2] Preserve streaming debug text in toString / printStackTrace
   
   Setting queryDebugString to an empty string avoids appending the debug 
section twice in getMessage, but StreamingQueryException.toString uses 
cause.getMessage when a cause exists and appends queryDebugString separately. 
The already-rendered outer message is therefore not used by toString in that 
case. Standard printStackTrace and exception logging lose the server debug 
section.
   
   Reproduced with an unknown condition, a server message ending in "server 
debug info", messageParameters("queryDebugString") = "server debug info", and 
cause = new IllegalStateException("server cause"):
   
   ```text
   Before: toString = ...StreamingQueryException: server cause
   server debug info
           printStackTrace contains server debug info: true
   After:  toString = ...StreamingQueryException: server cause
   
           printStackTrace contains server debug info: false
   ```
   
   Please retain the debug text for toString while suppressing duplicate 
formatting in getMessage, or make the server-message toString path include the 
already-formatted message. Add a regression assertion using a non-null cause; 
the current tests only assert getMessage.



##########
sql/connect/common/src/main/scala/org/apache/spark/sql/connect/client/GrpcExceptionConverter.scala:
##########
@@ -406,7 +406,170 @@ private[client] object GrpcExceptionConverter {
         errorClass = Option(getErrorClassOrFallback(params)),
         messageParameters = errorParamsToMessageParameters(params),
         context = params.queryContext,
-        sqlState = getSqlStateOrFallback(params))))
+        sqlState = getSqlStateOrFallback(params)))).map { case (name, 
constructor) =>
+    name -> { (params: ErrorParams) =>
+      if (params.errorClass.exists(ec => 
!SparkThrowableHelper.isValidErrorClass(ec))) {
+        // Connector conditions and conditions from newer servers may not be 
defined locally.
+        // Use the server message verbatim: parameters in ErrorInfo may have 
been truncated.
+        val exception = serverErrorFactory(name)(resolveParams(params))
+        if (params.cause.isDefined && exception.getCause == null) {
+          exception.initCause(params.cause.get)
+        }
+        exception
+      } else {
+        constructor(params)
+      }
+    }
+  }
+
+  // Constructors that accept an already rendered message without consulting 
the local registry.
+  private lazy val serverErrorFactory: Map[String, ErrorParams => Throwable] = 
Map(

Review Comment:
   Simplification: keep both constructors in one registration
   
   This duplicates the same 18 mapped types from errorFactory, and the 
unknown-condition path later assumes serverErrorFactory(name) exists. A future 
mapping must be added to both maps or only its unknown-condition path fails 
with a missing map key.
   
   Could errorConstructor accept both the existing constructor and the 
server-message constructor, registered together per type, and select between 
them in that helper? This removes the second map, its lazy initialization, and 
the name-based lookup while retaining the existing known-condition behavior. 
For example:
   
   ```scala
   errorConstructor[IllegalArgumentException](
     localConstructor,
     serverMessageConstructor)
   ```
   
   I compiled this structure in a temporary copy and all 10 focused tests from 
#59449 passed. It mainly simplifies maintenance rather than dramatically 
reducing line count. A generic SparkException fallback would be much shorter, 
but would sacrifice the mapped exception type.



##########
sql/api/src/main/scala/org/apache/spark/sql/catalyst/parser/parsers.scala:
##########
@@ -173,21 +173,34 @@ case object ParseErrorListener extends BaseErrorListener {
  * A [[ParseException]] is an [[SparkException]] that is thrown during the 
parse process. It
  * contains fields and an extended error message that make reporting and 
diagnosing errors easier.
  */
-class ParseException private (
+class ParseException private[sql] (
     val command: Option[String],
     message: String,
     val start: Origin,
-    errorClass: Option[String] = None,
-    messageParameters: Map[String, String] = Map.empty,
-    queryContext: Array[QueryContext] = ParseException.getQueryContext())
+    errorClass: Option[String],
+    messageParameters: Map[String, String],
+    queryContext: Array[QueryContext],
+    override val sqlState: Option[String],
+    cause: Option[Throwable],
+    private val messagePreformatted: Boolean)

Review Comment:
   [P2] Preserve the server-message construction path in withCommand
   
   This adds a new state where ParseException carries an unknown condition and 
a preformatted message, but withCommand still constructs a new ParseException 
through the constructor that calls SparkThrowableHelper.getMessage(newCl, 
params). The new flag, SQLSTATE, and cause are not propagated.
   
   I reconstructed an exception through 
errorFactory(classOf[ParseException].getName) with errorClass = 
Some("SERVER_ONLY_ERROR"). getMessage returns the original server message, but 
invoking withCommand("SELECT 1") throws:
   
   ```text
   [INTERNAL_ERROR] Cannot find main error class 'SERVER_ONLY_ERROR' SQLSTATE: 
XX000
   ```
   
   For a preformatted server exception, copy the message, condition, 
parameters, query context, SQLSTATE, cause, and preformatted flag without 
consulting the local catalog. Keep the existing PARSE_SYNTAX_ERROR / 
empty-command conversion for local parse errors. Please add a regression test 
for this public copying method.



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