From b4c02e0c8d221540b3e198114508ebb0275844d8 Mon Sep 17 00:00:00 2001 From: Yunus Emre Ozen <94426948+ozenyunus35@users.noreply.github.com> Date: Thu, 15 Oct 2026 03:45:00 +0300 Subject: [PATCH] fix(spark): return NULL from parse_url for a malformed URL unless ANSI Closes #26341. --- .../spark/src/function/url/parse_url.rs | 35 ++++++++++++++++--- .../test_files/spark/url/parse_url.slt | 20 +++++++++++ 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/datafusion/spark/src/function/url/parse_url.rs b/datafusion/spark/src/function/url/parse_url.rs index c385f43e49343..bac4be6fdc8ad 100644 --- a/datafusion/spark/src/function/url/parse_url.rs +++ b/datafusion/spark/src/function/url/parse_url.rs @@ -193,8 +193,11 @@ impl ScalarUDFImpl for ParseUrl { } fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result { - let ScalarFunctionArgs { args, .. } = args; - make_scalar_function(spark_parse_url, vec![])(&args) + let enable_ansi_mode = args.config_options.execution.enable_ansi_mode; + make_scalar_function( + move |arrays: &[ArrayRef]| spark_parse_url(arrays, enable_ansi_mode), + vec![], + )(&args.args) } } @@ -213,8 +216,14 @@ impl ScalarUDFImpl for ParseUrl { /// - A string array with extracted URL components /// - `None` values where extraction failed or component doesn't exist /// - The output array type (StringArray or LargeStringArray) is determined by input types -fn spark_parse_url(args: &[ArrayRef]) -> Result { - spark_handled_parse_url(args, |x| x) +/// +/// Like Spark, a malformed URL is an error only in ANSI mode; otherwise it +/// yields NULL, as `try_parse_url` does. +fn spark_parse_url(args: &[ArrayRef], enable_ansi_mode: bool) -> Result { + spark_handled_parse_url(args, |x| match x { + Err(_) if !enable_ansi_mode => Ok(None), + result => result, + }) } pub fn spark_handled_parse_url( @@ -612,6 +621,24 @@ mod tests { Ok(()) } + #[test] + fn test_malformed_url_is_null_unless_ansi() -> Result<()> { + let urls = sa(&[ + Some("inva lid://spark.apache.org/path?query=1"), + Some("https://example.com/a"), + ]); + let parts = sa(&[Some("QUERY"), Some("PATH")]); + + let out = spark_parse_url(&[Arc::clone(&urls), Arc::clone(&parts)], false)?; + let out_sa = out.as_any().downcast_ref::().unwrap(); + assert!(out_sa.is_null(0)); + assert_eq!(out_sa.value(1), "/a"); + + let err = spark_parse_url(&[urls, parts], true).unwrap_err(); + assert!(format!("{err}").contains("The url is invalid")); + Ok(()) + } + #[test] fn test_invalid_arg_count() { let urls = sa(&[Some("https://example.com")]); diff --git a/datafusion/sqllogictest/test_files/spark/url/parse_url.slt b/datafusion/sqllogictest/test_files/spark/url/parse_url.slt index 31f63607bc63d..0064df7dea97f 100644 --- a/datafusion/sqllogictest/test_files/spark/url/parse_url.slt +++ b/datafusion/sqllogictest/test_files/spark/url/parse_url.slt @@ -343,9 +343,29 @@ NULL statement error 'parse_url' does not support zero arguments SELECT parse_url(); +# A malformed URL is NULL unless ANSI mode is on, as in Spark +query T +SELECT parse_url('inva lid://spark.apache.org/path?query=1', 'QUERY'); +---- +NULL + +query T +SELECT parse_url('http://example.com:99999/', 'HOST'); +---- +NULL + +statement ok +set datafusion.execution.enable_ansi_mode = true; + query error DataFusion error: Execution error: The url is invalid: inva lid://spark\.apache\.org/path\?query=1\. Use `try_parse_url` to tolerate invalid URL and return NULL instead\. SQLSTATE: 22P02 SELECT parse_url('inva lid://spark.apache.org/path?query=1', 'QUERY'); +query error DataFusion error: Execution error: InvalidPort +SELECT parse_url('http://example.com:99999/', 'HOST'); + +statement ok +set datafusion.execution.enable_ansi_mode = false; + # NULL argument handling (Sail PR #1393) # NULL URL should return NULL query T