@@ -383,55 +383,52 @@ object CometStringSplit extends CometExpressionSerde[StringSplit] {
383383 }
384384}
385385
386- object CometParseUrl extends CometExpressionSerde [ParseUrl ] {
386+ object CometParseUrl extends CometExpressionSerde [ParseUrl ] with CometInvokeExpressionSerde [ ParseUrl ] {
387387
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 =
388+ // In Spark 4.0, ParseUrl became RuntimeReplaceable and the analyser rewrites it to
389+ // Invoke(ParseUrlEvaluator.evaluate, url, part[, key]). The first child is a
390+ // Literal(evaluator, ObjectType(ParseUrlEvaluator)). This class name is the key
391+ // used by QueryPlanSerde to route the Invoke node to this handler.
392+ override val invokeTargetClassName : String =
392393 " org.apache.spark.sql.catalyst.expressions.url.ParseUrlEvaluator"
393394
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
395+ // Extracts the failOnError flag from the ParseUrlEvaluator instance via reflection.
396+ // Falls back to SQLConf.get.ansiEnabled when reflection is unavailable (null evaluator
397+ // or renamed accessor in a future Spark version).
398+ private def failOnErrorFromEvaluator (evaluator : AnyRef ): Boolean =
399+ try {
400+ evaluator.getClass.getMethod(" failOnError" ).invoke(evaluator).asInstanceOf [Boolean ]
401+ } catch {
402+ case _ : ReflectiveOperationException => SQLConf .get.ansiEnabled
416403 }
417404
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 ))
405+ override def convertFromInvoke (
406+ expr : ParseUrl ,
407+ inputs : Seq [Attribute ],
408+ binding : Boolean ): Option [Expr ] = {
409+ // The first child is Literal(evaluator, ObjectType(ParseUrlEvaluator)).
410+ // Strip it and read failOnError from it; the remaining children are (url, part[, key]).
411+ val (urlArgs, failOnError) = expr.children match {
412+ case Literal (evaluator, objectType : ObjectType ) +: rest
423413 if objectType.cls.getName == invokeTargetClassName =>
424- children.drop(1 )
425- case _ =>
426- children
414+ val foe =
415+ if (evaluator != null ) failOnErrorFromEvaluator(evaluator.asInstanceOf [AnyRef ])
416+ else SQLConf .get.ansiEnabled
417+ (rest, foe)
418+ case args =>
419+ (args, SQLConf .get.ansiEnabled)
427420 }
421+ toProto(expr, urlArgs, failOnError, inputs, binding)
422+ }
423+
424+ // In Spark 3.5, ParseUrl is a concrete expression node with a `failOnError` field
425+ // that is directly accessible without reflection.
426+ override def convert (expr : ParseUrl , inputs : Seq [Attribute ], binding : Boolean ): Option [Expr ] =
427+ toProto(expr, expr.children, expr.failOnError, inputs, binding)
428428
429- // ---------------------------------------------------------------------------
430- // Core serialization
431- // ---------------------------------------------------------------------------
432429
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 .
430+ // Serializes (url, part[, key]) arguments into the appropriate native function call .
431+ // Uses parse_url (ANSI/strict) or try_parse_url (legacy/lenient) depending on failOnError.
435432 private def toProto (
436433 expr : Expression ,
437434 urlArgs : Seq [Expression ],
@@ -443,25 +440,6 @@ object CometParseUrl extends CometExpressionSerde[ParseUrl] {
443440 val optExpr = scalarFunctionExprToProto(functionName, childExprs : _* )
444441 optExprWithInfo(optExpr, expr, urlArgs : _* )
445442 }
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- }
465443}
466444
467445trait CommonStringExprs {
0 commit comments