@@ -383,55 +383,53 @@ object CometStringSplit extends CometExpressionSerde[StringSplit] {
383383 }
384384}
385385
386- object CometParseUrl extends CometExpressionSerde [ParseUrl ] {
387-
388- // Class name of the Spark 4.0 internal evaluator embedded in the Invoke node that replaces
389- // ParseUrl at analysis time (RuntimeReplaceable). This constant is used as the dispatch key
390- // in QueryPlanSerde.invokeConvertersByTargetClassName.
391- val invokeTargetClassName : String =
386+ object CometParseUrl
387+ extends CometExpressionSerde [ParseUrl ]
388+ with CometInvokeExpressionSerde [ParseUrl ] {
389+
390+ // In Spark 4.0, ParseUrl became RuntimeReplaceable and the analyser rewrites it to
391+ // Invoke(ParseUrlEvaluator.evaluate, url, part[, key]). The first child is a
392+ // Literal(evaluator, ObjectType(ParseUrlEvaluator)). This class name is the key
393+ // used by QueryPlanSerde to route the Invoke node to this handler.
394+ override val invokeTargetClassName : String =
392395 " org.apache.spark.sql.catalyst.expressions.url.ParseUrlEvaluator"
393396
394- // ---------------------------------------------------------------------------
395- // Spark 4.0 Invoke path helpers
396- // ---------------------------------------------------------------------------
397-
398- // Extracts the failOnError flag from the ParseUrlEvaluator instance embedded in the
399- // Invoke literal. Uses reflection because ParseUrlEvaluator is a private class.
400- // Falls back to SQLConf.get.ansiEnabled when reflection fails (evaluator is null, or
401- // the method has been renamed in a future Spark version).
402- private [serde] def failOnErrorFromInvoke (expr : Expression ): Boolean =
403- expr.children.headOption match {
404- case Some (Literal (evaluator, objectType : ObjectType ))
405- if evaluator != null && objectType.cls.getName == invokeTargetClassName =>
406- try {
407- evaluator.getClass
408- .getMethod(" failOnError" )
409- .invoke(evaluator)
410- .asInstanceOf [Boolean ]
411- } catch {
412- case _ : ReflectiveOperationException => SQLConf .get.ansiEnabled
413- }
414- case _ =>
415- SQLConf .get.ansiEnabled
397+ // Extracts the failOnError flag from the ParseUrlEvaluator instance via reflection.
398+ // Falls back to SQLConf.get.ansiEnabled when reflection is unavailable (null evaluator
399+ // or renamed accessor in a future Spark version).
400+ private def failOnErrorFromEvaluator (evaluator : AnyRef ): Boolean =
401+ try {
402+ evaluator.getClass.getMethod(" failOnError" ).invoke(evaluator).asInstanceOf [Boolean ]
403+ } catch {
404+ case _ : ReflectiveOperationException => SQLConf .get.ansiEnabled
416405 }
417406
418- // Drops the leading ParseUrlEvaluator literal from the Invoke children list, leaving
419- // only the actual URL/part/key arguments.
420- private def dropEvaluatorLiteral (children : Seq [Expression ]): Seq [Expression ] =
421- children.headOption match {
422- case Some (Literal (_, objectType : ObjectType ))
407+ override def convertFromInvoke (
408+ expr : ParseUrl ,
409+ inputs : Seq [Attribute ],
410+ binding : Boolean ): Option [Expr ] = {
411+ // The first child is Literal(evaluator, ObjectType(ParseUrlEvaluator)).
412+ // Strip it and read failOnError from it; the remaining children are (url, part[, key]).
413+ val (urlArgs, failOnError) = expr.children match {
414+ case Literal (evaluator, objectType : ObjectType ) +: rest
423415 if objectType.cls.getName == invokeTargetClassName =>
424- children.drop(1 )
425- case _ =>
426- children
416+ val foe =
417+ if (evaluator != null ) failOnErrorFromEvaluator(evaluator.asInstanceOf [AnyRef ])
418+ else SQLConf .get.ansiEnabled
419+ (rest, foe)
420+ case args =>
421+ (args, SQLConf .get.ansiEnabled)
427422 }
423+ toProto(expr, urlArgs, failOnError, inputs, binding)
424+ }
428425
429- // ---------------------------------------------------------------------------
430- // Core serialization
431- // ---------------------------------------------------------------------------
426+ // In Spark 3.5, ParseUrl is a concrete expression node with a `failOnError` field
427+ // that is directly accessible without reflection.
428+ override def convert (expr : ParseUrl , inputs : Seq [Attribute ], binding : Boolean ): Option [Expr ] =
429+ toProto(expr, expr.children, expr.failOnError, inputs, binding)
432430
433- // Converts the parse_url/try_parse_url arguments into a proto Expr .
434- // `urlArgs` must already be stripped of the evaluator literal and the failOnError flag literal .
431+ // Serializes (url, part[, key]) arguments into the appropriate native function call .
432+ // Uses parse_url (ANSI/strict) or try_parse_url (legacy/lenient) depending on failOnError.
435433 private def toProto (
436434 expr : Expression ,
437435 urlArgs : Seq [Expression ],
@@ -443,25 +441,6 @@ object CometParseUrl extends CometExpressionSerde[ParseUrl] {
443441 val optExpr = scalarFunctionExprToProto(functionName, childExprs : _* )
444442 optExprWithInfo(optExpr, expr, urlArgs : _* )
445443 }
446-
447- // ---------------------------------------------------------------------------
448- // Public entry points
449- // ---------------------------------------------------------------------------
450-
451- // Spark 3.5 path: ParseUrl is a concrete expression node with a `failOnError` field.
452- override def convert (expr : ParseUrl , inputs : Seq [Attribute ], binding : Boolean ): Option [Expr ] =
453- toProto(expr, expr.children, expr.failOnError, inputs, binding)
454-
455- // Spark 4.0 path: ParseUrl is replaced by Invoke(ParseUrlEvaluator, url, part[, key]).
456- // Called from QueryPlanSerde.convertInvokeExpression via invokeConvertersByTargetClassName.
457- def convertFromInvoke (
458- expr : Expression ,
459- inputs : Seq [Attribute ],
460- binding : Boolean ): Option [Expr ] = {
461- val failOnError = failOnErrorFromInvoke(expr)
462- val urlArgs = dropEvaluatorLiteral(expr.children)
463- toProto(expr, urlArgs, failOnError, inputs, binding)
464- }
465444}
466445
467446trait CommonStringExprs {
0 commit comments