This is an automated email from the ASF dual-hosted git repository.

yiguolei pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.2 by this push:
     new 55b477ec6bb branch-4.2: [fix](function) Align parse_url boundaries and 
nullable execution #68490 (#70131)
55b477ec6bb is described below

commit 55b477ec6bb5dbc7781816333d469c9f6e688f8a
Author: morrySnow <[email protected]>
AuthorDate: Thu Nov 5 09:30:23 2026 +0800

    branch-4.2: [fix](function) Align parse_url boundaries and nullable 
execution #68490 (#70131)
    
    picked from master #68490
    
    Co-authored-by: Mryange <[email protected]>
---
 be/src/exprs/function/function_string_url.cpp      | 148 ++++++++-------------
 be/src/util/url_parser.cpp                         | 136 +++++++++++--------
 be/src/util/url_parser.h                           |   4 +
 be/test/exprs/function/function_url_test.cpp       |  47 +++++++
 .../functions/executable/StringArithmetic.java     |  86 ++++++++----
 .../functions/executable/StringArithmeticTest.java |  41 ++++++
 6 files changed, 288 insertions(+), 174 deletions(-)

diff --git a/be/src/exprs/function/function_string_url.cpp 
b/be/src/exprs/function/function_string_url.cpp
index 3592356f98d..dcdaa6fccf7 100644
--- a/be/src/exprs/function/function_string_url.cpp
+++ b/be/src/exprs/function/function_string_url.cpp
@@ -18,13 +18,13 @@
 #include <cstddef>
 #include <string>
 #include <string_view>
-#include <vector>
 
 #include "common/status.h"
 #include "core/assert_cast.h"
 #include "core/block/block.h"
 #include "core/block/column_numbers.h"
 #include "core/column/column_const.h"
+#include "core/column/column_execute_util.h"
 #include "core/column/column_nullable.h"
 #include "core/column/column_string.h"
 #include "core/column/column_vector.h"
@@ -32,7 +32,6 @@
 #include "core/data_type/data_type_string.h"
 #include "core/string_ref.h"
 #include "exec/common/stringop_substring.h"
-#include "exec/common/template_helpers.hpp"
 #include "exprs/function/function.h"
 #include "exprs/function/function_helpers.h"
 #include "exprs/function/simple_function_factory.h"
@@ -93,6 +92,7 @@ public:
     String get_name() const override { return name; }
     size_t get_number_of_arguments() const override { return 0; }
     bool is_variadic() const override { return true; }
+    bool use_default_implementation_for_nulls() const override { return false; 
}
 
     DataTypePtr get_return_type_impl(const DataTypes& arguments) const 
override {
         return make_nullable(std::make_shared<DataTypeString>());
@@ -111,101 +111,67 @@ public:
         size_t argument_size = arguments.size();
         const bool has_key = argument_size == 3;
 
-        std::vector<ColumnPtr> argument_columns(argument_size);
-        std::vector<UInt8> col_const(argument_size);
-        for (size_t i = 0; i < argument_size; ++i) {
-            std::tie(argument_columns[i], col_const[i]) =
-                    
unpack_if_const(block.get_by_position(arguments[i]).column);
-        }
-
-        const auto* url_col = assert_cast<const 
ColumnString*>(argument_columns[0].get());
-        const auto* part_col = assert_cast<const 
ColumnString*>(argument_columns[1].get());
-        const bool part_const = col_const[1];
-        std::vector<UrlParser::UrlPart> url_parts;
-        const int part_nums = part_const ? 1 : input_rows_count;
-
-        url_parts.resize(part_nums);
-        for (int i = 0; i < part_nums; i++) {
-            StringRef part = part_col->get_data_at(i);
-            UrlParser::UrlPart url_part = UrlParser::get_url_part(part);
-            if (url_part == UrlParser::INVALID) {
-                return Status::RuntimeError("Invalid URL part: {}\n{}",
-                                            std::string(part.data, part.size),
-                                            "(Valid URL parts are 'PROTOCOL', 
'HOST', "
-                                            "'PATH', 'REF', 'AUTHORITY', "
-                                            "'FILE', 'USERINFO', 'PORT' and 
'QUERY')");
-            }
-            url_parts[i] = url_part;
-        }
-
+        const auto url_col =
+                
ColumnView<TYPE_STRING>::create(block.get_by_position(arguments[0]).column);
+        const auto part_col =
+                
ColumnView<TYPE_STRING>::create(block.get_by_position(arguments[1]).column);
         if (has_key) {
-            const bool url_const = col_const[0];
-            const bool key_const = col_const[2];
-            const auto* key_col = assert_cast<const 
ColumnString*>(argument_columns[2].get());
-            RETURN_IF_ERROR(std::visit(
-                    [&](auto url_const, auto part_const, auto key_const) {
-                        return vector_parse_key<url_const, part_const, 
key_const>(
-                                url_col, url_parts, key_col, input_rows_count, 
null_map_data,
-                                res_chars, res_offsets);
-                    },
-                    make_bool_variant(url_const), 
make_bool_variant(part_const),
-                    make_bool_variant(key_const)));
-        } else {
-            const bool url_const = col_const[0];
-            RETURN_IF_ERROR(std::visit(
-                    [&](auto url_const, auto part_const) {
-                        return vector_parse<url_const, part_const>(url_col, 
url_parts,
-                                                                   
input_rows_count, null_map_data,
-                                                                   res_chars, 
res_offsets);
-                    },
-                    make_bool_variant(url_const), 
make_bool_variant(part_const)));
-        }
-        block.get_by_position(result).column =
-                ColumnNullable::create(std::move(res), std::move(null_map));
-        return Status::OK();
-    }
-    template <bool url_const, bool part_const>
-    static Status vector_parse(const ColumnString* url_col,
-                               std::vector<UrlParser::UrlPart>& url_parts, 
const int size,
-                               ColumnUInt8::Container& null_map_data,
-                               ColumnString::Chars& res_chars, 
ColumnString::Offsets& res_offsets) {
-        for (size_t i = 0; i < size; ++i) {
-            UrlParser::UrlPart& url_part = 
url_parts[index_check_const<part_const>(i)];
-            StringRef url_val = 
url_col->get_data_at(index_check_const<url_const>(i));
-            StringRef parse_res;
-            if (UrlParser::parse_url(url_val, url_part, &parse_res)) {
-                if (parse_res.empty()) [[unlikely]] {
-                    StringOP::push_empty_string(i, res_chars, res_offsets);
+            const auto key_col =
+                    
ColumnView<TYPE_STRING>::create(block.get_by_position(arguments[2]).column);
+            for (size_t i = 0; i < input_rows_count; ++i) {
+                if (url_col.is_null_at(i) || part_col.is_null_at(i) || 
key_col.is_null_at(i)) {
+                    StringOP::push_null_string(i, res_chars, res_offsets, 
null_map_data);
                     continue;
                 }
-                StringOP::push_value_string(std::string_view(parse_res.data, 
parse_res.size), i,
-                                            res_chars, res_offsets);
-            } else {
-                StringOP::push_null_string(i, res_chars, res_offsets, 
null_map_data);
+                const auto part = part_col.value_at(i);
+                const auto url_part = UrlParser::get_url_part(part);
+                if (url_part == UrlParser::INVALID) {
+                    return Status::RuntimeError("Invalid URL part: {}\n{}",
+                                                std::string(part.data, 
part.size),
+                                                "(Valid URL parts are 
'PROTOCOL', 'HOST', "
+                                                "'PATH', 'REF', 'AUTHORITY', "
+                                                "'FILE', 'USERINFO', 'PORT' 
and 'QUERY')");
+                }
+                StringRef parse_res;
+                if (UrlParser::parse_url_key(url_col.value_at(i), url_part, 
key_col.value_at(i),
+                                             &parse_res)) {
+                    
StringOP::push_value_string(std::string_view(parse_res.data, parse_res.size), i,
+                                                res_chars, res_offsets);
+                } else {
+                    StringOP::push_null_string(i, res_chars, res_offsets, 
null_map_data);
+                }
             }
-        }
-        return Status::OK();
-    }
-    template <bool url_const, bool part_const, bool key_const>
-    static Status vector_parse_key(const ColumnString* url_col,
-                                   std::vector<UrlParser::UrlPart>& url_parts,
-                                   const ColumnString* key_col, const int size,
-                                   ColumnUInt8::Container& null_map_data,
-                                   ColumnString::Chars& res_chars,
-                                   ColumnString::Offsets& res_offsets) {
-        for (size_t i = 0; i < size; ++i) {
-            UrlParser::UrlPart& url_part = 
url_parts[index_check_const<part_const>(i)];
-            StringRef url_val = 
url_col->get_data_at(index_check_const<url_const>(i));
-            StringRef url_key = 
key_col->get_data_at(index_check_const<key_const>(i));
-            StringRef parse_res;
-            if (UrlParser::parse_url_key(url_val, url_part, url_key, 
&parse_res)) {
-                StringOP::push_value_string(std::string_view(parse_res.data, 
parse_res.size), i,
-                                            res_chars, res_offsets);
-            } else {
-                StringOP::push_null_string(i, res_chars, res_offsets, 
null_map_data);
-                continue;
+        } else {
+            for (size_t i = 0; i < input_rows_count; ++i) {
+                if (url_col.is_null_at(i) || part_col.is_null_at(i)) {
+                    StringOP::push_null_string(i, res_chars, res_offsets, 
null_map_data);
+                    continue;
+                }
+                const auto part = part_col.value_at(i);
+                const auto url_part = UrlParser::get_url_part(part);
+                if (url_part == UrlParser::INVALID) {
+                    return Status::RuntimeError("Invalid URL part: {}\n{}",
+                                                std::string(part.data, 
part.size),
+                                                "(Valid URL parts are 
'PROTOCOL', 'HOST', "
+                                                "'PATH', 'REF', 'AUTHORITY', "
+                                                "'FILE', 'USERINFO', 'PORT' 
and 'QUERY')");
+                }
+                StringRef parse_res;
+                if (UrlParser::parse_url(url_col.value_at(i), url_part, 
&parse_res)) {
+                    if (parse_res.empty()) {
+                        StringOP::push_empty_string(i, res_chars, res_offsets);
+                    } else {
+                        StringOP::push_value_string(
+                                std::string_view(parse_res.data, 
parse_res.size), i, res_chars,
+                                res_offsets);
+                    }
+                } else {
+                    StringOP::push_null_string(i, res_chars, res_offsets, 
null_map_data);
+                }
             }
         }
+        block.get_by_position(result).column =
+                ColumnNullable::create(std::move(res), std::move(null_map));
         return Status::OK();
     }
 };
diff --git a/be/src/util/url_parser.cpp b/be/src/util/url_parser.cpp
index 292ed950876..e168a999910 100644
--- a/be/src/util/url_parser.cpp
+++ b/be/src/util/url_parser.cpp
@@ -75,6 +75,21 @@ bool UrlParser::find_query_component(const StringRef& url, 
StringRef* query) {
     return true;
 }
 
+StringRef UrlParser::find_authority(const StringRef& protocol_end) {
+    // The authority component runs from the end of '://' up to the first '/', 
'?' or '#',
+    // whichever comes first.
+    int32_t end_pos = _s_slash_search.search(&protocol_end);
+    int32_t question_pos = _s_question_search.search(&protocol_end);
+    if (question_pos >= 0 && (end_pos < 0 || question_pos < end_pos)) {
+        end_pos = question_pos;
+    }
+    int32_t hash_pos = _s_hash_search.search(&protocol_end);
+    if (hash_pos >= 0 && (end_pos < 0 || hash_pos < end_pos)) {
+        end_pos = hash_pos;
+    }
+    return protocol_end.substring(0, end_pos);
+}
+
 bool UrlParser::parse_url(const StringRef& url, UrlPart part, StringRef* 
result) {
     result->data = nullptr;
     result->size = 0;
@@ -92,18 +107,21 @@ bool UrlParser::parse_url(const StringRef& url, UrlPart 
part, StringRef* result)
 
     switch (part) {
     case AUTHORITY: {
-        // Find first '/'.
-        int32_t end_pos = _s_slash_search.search(&protocol_end);
-        *result = protocol_end.substring(0, end_pos);
+        *result = find_authority(protocol_end);
         break;
     }
 
     case FILE:
     case PATH: {
-        // Find first '/'.
-        int32_t start_pos = _s_slash_search.search(&protocol_end);
-
-        if (start_pos < 0) {
+        int32_t slash_pos = _s_slash_search.search(&protocol_end);
+        int32_t question_pos = _s_question_search.search(&protocol_end);
+        int32_t hash_pos = _s_hash_search.search(&protocol_end);
+        int32_t start_pos = slash_pos;
+        if (part == FILE && (start_pos < 0 || (question_pos >= 0 && 
question_pos < start_pos))) {
+            start_pos = question_pos;
+        }
+        if (start_pos < 0 || (part == PATH && question_pos >= 0 && 
question_pos < start_pos) ||
+            (hash_pos >= 0 && hash_pos < start_pos)) {
             // Return empty string. This is what Hive does.
             return true;
         }
@@ -117,10 +135,9 @@ bool UrlParser::parse_url(const StringRef& url, UrlPart 
part, StringRef* result)
         } else {
             // End string _s_at next '?' or '#'.
             end_pos = _s_question_search.search(&path_start);
-
-            if (end_pos < 0) {
-                // No '?' was found, look for '#'.
-                end_pos = _s_hash_search.search(&path_start);
+            int32_t path_hash_pos = _s_hash_search.search(&path_start);
+            if (end_pos < 0 || (path_hash_pos >= 0 && path_hash_pos < 
end_pos)) {
+                end_pos = path_hash_pos;
             }
         }
 
@@ -129,31 +146,29 @@ bool UrlParser::parse_url(const StringRef& url, UrlPart 
part, StringRef* result)
     }
 
     case HOST: {
-        // Find '@'.
-        int32_t start_pos = _s_at_search.search(&protocol_end);
-
-        if (start_pos < 0) {
-            // No '@' was found, i.e., no user:pass info was given, start 
after _s_protocol.
-            start_pos = 0;
-        } else {
-            // Skip '@'.
-            start_pos += _s_at.size;
-        }
-
-        StringRef host_start = protocol_end.substring(start_pos);
-        // Find first '?'.
-        int32_t query_start_pos = _s_question_search.search(&host_start);
-        if (query_start_pos > 0) {
-            host_start = host_start.substring(0, query_start_pos);
+        StringRef authority = find_authority(protocol_end);
+        int32_t userinfo_end = -1;
+        for (int32_t i = 0; std::cmp_less(i, authority.size); ++i) {
+            if (authority.data[i] == '@') {
+                userinfo_end = i;
+            }
         }
-        // Find ':' to strip out port.
-        int32_t end_pos = _s_colon_search.search(&host_start);
 
-        if (end_pos < 0) {
-            // No port was given. search for '/' to determine ending position.
-            end_pos = _s_slash_search.search(&host_start);
+        StringRef host_start = authority.substring(userinfo_end + 1);
+        auto end_pos = cast_set<int32_t>(host_start.size);
+        if (!host_start.empty() && host_start.data[0] == '[') {
+            for (int32_t i = 1; std::cmp_less(i, host_start.size); ++i) {
+                if (host_start.data[i] == ']') {
+                    end_pos = i + 1;
+                    break;
+                }
+            }
+        } else {
+            int32_t colon_pos = _s_colon_search.search(&host_start);
+            if (colon_pos >= 0) {
+                end_pos = colon_pos;
+            }
         }
-
         *result = host_start.substring(0, end_pos);
         break;
     }
@@ -190,45 +205,54 @@ bool UrlParser::parse_url(const StringRef& url, UrlPart 
part, StringRef* result)
     }
 
     case USERINFO: {
-        // Find '@'.
-        int32_t end_pos = _s_at_search.search(&protocol_end);
+        StringRef authority = find_authority(protocol_end);
+        int32_t end_pos = -1;
+        for (int32_t i = 0; std::cmp_less(i, authority.size); ++i) {
+            if (authority.data[i] == '@') {
+                end_pos = i;
+            }
+        }
 
         if (end_pos < 0) {
             // Indicate no user and pass were given.
             return false;
         }
 
-        *result = protocol_end.substring(0, end_pos);
+        *result = authority.substring(0, end_pos);
         break;
     }
 
     case PORT: {
-        // Find '@'.
-        int32_t start_pos = _s_at_search.search(&protocol_end);
+        StringRef authority = find_authority(protocol_end);
+        int32_t userinfo_end = -1;
+        for (int32_t i = 0; std::cmp_less(i, authority.size); ++i) {
+            if (authority.data[i] == '@') {
+                userinfo_end = i;
+            }
+        }
 
-        if (start_pos < 0) {
-            // No '@' was found, i.e., no user:pass info was given, start 
after _s_protocol.
-            start_pos = 0;
+        StringRef host_start = authority.substring(userinfo_end + 1);
+        int32_t port_start = -1;
+        if (!host_start.empty() && host_start.data[0] == '[') {
+            for (int32_t i = 1; std::cmp_less(i, host_start.size); ++i) {
+                if (host_start.data[i] == ']') {
+                    if (std::cmp_less(i + 1, host_start.size) && 
host_start.data[i + 1] == ':') {
+                        port_start = i + 2;
+                    }
+                    break;
+                }
+            }
         } else {
-            // Skip '@'.
-            start_pos += _s_at.size;
+            int32_t colon_pos = _s_colon_search.search(&host_start);
+            if (colon_pos >= 0) {
+                port_start = colon_pos + 1;
+            }
         }
-
-        StringRef host_start = protocol_end.substring(start_pos);
-        // Find ':' to strip out port.
-        int32_t end_pos = _s_colon_search.search(&host_start);
-        //no port found
-        if (end_pos < 0) {
+        if (port_start < 0) {
             return false;
         }
 
-        StringRef port_start_str = host_start.substring(end_pos + 
_s_colon.size);
-        int32_t port_end_pos = _s_slash_search.search(&port_start_str);
-        //if '/' not found, try to find '?'
-        if (port_end_pos < 0) {
-            port_end_pos = _s_question_search.search(&port_start_str);
-        }
-        *result = port_start_str.substring(0, port_end_pos);
+        *result = host_start.substring(port_start);
         break;
     }
 
diff --git a/be/src/util/url_parser.h b/be/src/util/url_parser.h
index 790a5c536cb..41c876daeaf 100644
--- a/be/src/util/url_parser.h
+++ b/be/src/util/url_parser.h
@@ -72,6 +72,10 @@ private:
     // '#' comes before the first '?' because the '?' then belongs to the 
fragment.
     static bool find_query_component(const StringRef& url, StringRef* query);
 
+    // Returns the authority component of url, which has already had its 
protocol stripped.
+    // The authority ends at the first '/', '?' or '#'.
+    static StringRef find_authority(const StringRef& protocol_end);
+
     // Constants representing parts of a URL.
     static const StringRef _s_url_authority;
     static const StringRef _s_url_file;
diff --git a/be/test/exprs/function/function_url_test.cpp 
b/be/test/exprs/function/function_url_test.cpp
index 7fc20fb104c..263346170c7 100644
--- a/be/test/exprs/function/function_url_test.cpp
+++ b/be/test/exprs/function/function_url_test.cpp
@@ -99,6 +99,10 @@ TEST(FunctionUrlTEST, ParseUrlQueryKeyTest) {
                                 PrimitiveType::TYPE_VARCHAR};
 
     DataSet data_set = {
+            // Every nullable argument must short-circuit independently in the 
keyed form.
+            {{Null(), STRING("QUERY"), STRING("k")}, Null()},
+            {{STRING("http://h/p?k=1";), Null(), STRING("k")}, Null()},
+            {{STRING("http://h/p?k=1";), STRING("QUERY"), Null()}, Null()},
             // The only '?' is inside the fragment, so the url has no query 
component.
             {{STRING("http://h/p#f?k=v";), STRING("QUERY"), STRING("k")}, 
Null()},
             // The '#' comes before the '?', so it is a fragment instead of a 
query.
@@ -161,4 +165,47 @@ TEST(FunctionUrlTEST, ParseUrlQueryTest) {
     static_cast<void>(check_function<DataTypeString, true>(func_name, 
input_types, data_set));
 }
 
+TEST(FunctionUrlTEST, ParseUrlAuthorityTest) {
+    std::string func_name = "parse_url";
+    InputTypeSet input_types = {PrimitiveType::TYPE_VARCHAR, 
PrimitiveType::TYPE_VARCHAR};
+
+    DataSet data_set = {
+            // A nullable dynamic part must be handled row by row. A NULL part 
must not be
+            // materialized as an invalid empty part while another row is 
being parsed.
+            {{Null(), STRING("HOST")}, Null()},
+            {{STRING("http://example.com/path?x=1";), STRING("HOST")}, 
STRING("example.com")},
+            {{STRING("http://example.com/path?x=1";), Null()}, Null()},
+            // A ':' in the path is not a port separator, and an '@' in the 
path is not a
+            // userinfo separator.
+            {{STRING("http://example.com/a:b";), STRING("HOST")}, 
STRING("example.com")},
+            {{STRING("http://example.com/a:b";), STRING("PORT")}, Null()},
+            {{STRING("http://example.com/a:b";), STRING("AUTHORITY")}, 
STRING("example.com")},
+            {{STRING("http://example.com/a@b:c";), STRING("HOST")}, 
STRING("example.com")},
+            {{STRING("http://example.com/a@b:c";), STRING("USERINFO")}, Null()},
+            // A ':' in the query or the fragment is not a port separator 
either.
+            {{STRING("http://example.com/p?r=http:8080";), STRING("PORT")}, 
Null()},
+            {{STRING("http://example.com#f:1";), STRING("HOST")}, 
STRING("example.com")},
+            {{STRING("http://example.com#f:1";), STRING("PORT")}, Null()},
+            {{STRING("http://example.com?x=1";), STRING("AUTHORITY")}, 
STRING("example.com")},
+            {{STRING("http://[2001:db8::1]:8080/a?x=1#r";), STRING("HOST")},
+             STRING("[2001:db8::1]")},
+            {{STRING("http://[2001:db8::1]:8080/a?x=1#r";), STRING("PORT")}, 
STRING("8080")},
+            {{STRING("http://h/p#frag?x=1";), STRING("PATH")}, STRING("/p")},
+            {{STRING("http://h/p#frag?x=1";), STRING("QUERY")}, Null()},
+            // FILE includes a query even when the URL has no path, while PATH 
remains empty.
+            {{STRING("http://h?k=/v#frag";), STRING("FILE")}, STRING("?k=/v")},
+            {{STRING("http://h?k=/v#frag";), STRING("PATH")}, STRING("")},
+            // A real port and a real userinfo are still returned.
+            {{STRING("http://user:[email protected]:80/a:b";), STRING("HOST")},
+             STRING("example.com")},
+            {{STRING("http://user:[email protected]:80/a:b";), STRING("PORT")}, 
STRING("80")},
+            {{STRING("http://user:[email protected]:80/a:b";), 
STRING("USERINFO")},
+             STRING("user:pass")},
+            {{STRING("http://user:[email protected]:80/a:b";), 
STRING("AUTHORITY")},
+             STRING("user:[email protected]:80")},
+    };
+
+    static_cast<void>(check_function<DataTypeString, true>(func_name, 
input_types, data_set));
+}
+
 } // namespace doris
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/executable/StringArithmetic.java
 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/executable/StringArithmetic.java
index 3a422fda284..98a79583168 100644
--- 
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/executable/StringArithmetic.java
+++ 
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/expressions/functions/executable/StringArithmetic.java
@@ -987,17 +987,39 @@ public class StringArithmetic {
         return Math.min(firstIndex, secondIndex);
     }
 
+    private static int firstIndexOf(String value, char first, char second, 
char third) {
+        return firstIndexOf(value, firstIndexOf(value, first, second), third);
+    }
+
+    private static int firstIndexOf(String value, int firstIndex, char second) 
{
+        int secondIndex = value.indexOf(second);
+        if (firstIndex < 0) {
+            return secondIndex;
+        }
+        if (secondIndex < 0) {
+            return firstIndex;
+        }
+        return Math.min(firstIndex, secondIndex);
+    }
+
     private static String substringEnd(String value, int end) {
         return end < 0 ? value : value.substring(0, end);
     }
 
     private static String parseUrlAuthority(String protocolEnd) {
-        return substringEnd(protocolEnd, protocolEnd.indexOf('/'));
+        // The authority component runs from the end of "://" up to the first 
'/', '?' or '#',
+        // whichever comes first.
+        int endPos = firstIndexOf(protocolEnd, '?', '#');
+        int slashPos = protocolEnd.indexOf('/');
+        if (slashPos >= 0 && (endPos < 0 || slashPos < endPos)) {
+            endPos = slashPos;
+        }
+        return substringEnd(protocolEnd, endPos);
     }
 
     private static String parseUrlPath(String protocolEnd) {
-        int startPos = protocolEnd.indexOf('/');
-        if (startPos < 0) {
+        int startPos = firstIndexOf(protocolEnd, '/', '?', '#');
+        if (startPos < 0 || protocolEnd.charAt(startPos) != '/') {
             return "";
         }
         String pathStart = protocolEnd.substring(startPos);
@@ -1006,7 +1028,12 @@ public class StringArithmetic {
 
     private static String parseUrlFile(String protocolEnd) {
         int startPos = protocolEnd.indexOf('/');
-        if (startPos < 0) {
+        int queryPos = protocolEnd.indexOf('?');
+        int fragmentPos = protocolEnd.indexOf('#');
+        if (startPos < 0 || (queryPos >= 0 && queryPos < startPos)) {
+            startPos = queryPos;
+        }
+        if (startPos < 0 || (fragmentPos >= 0 && fragmentPos < startPos)) {
             return "";
         }
         String pathStart = protocolEnd.substring(startPos);
@@ -1014,18 +1041,15 @@ public class StringArithmetic {
     }
 
     private static String parseUrlHost(String protocolEnd) {
-        int startPos = protocolEnd.indexOf('@');
+        String authority = parseUrlAuthority(protocolEnd);
+        int startPos = authority.lastIndexOf('@');
         startPos = startPos < 0 ? 0 : startPos + 1;
-        String hostStart = protocolEnd.substring(startPos);
-        int queryStartPos = hostStart.indexOf('?');
-        if (queryStartPos > 0) {
-            hostStart = hostStart.substring(0, queryStartPos);
-        }
-        int endPos = hostStart.indexOf(':');
-        if (endPos < 0) {
-            endPos = hostStart.indexOf('/');
+        String hostStart = authority.substring(startPos);
+        if (hostStart.startsWith("[")) {
+            int closeBracket = hostStart.indexOf(']');
+            return closeBracket < 0 ? hostStart : hostStart.substring(0, 
closeBracket + 1);
         }
-        return substringEnd(hostStart, endPos);
+        return substringEnd(hostStart, hostStart.indexOf(':'));
     }
 
     private static String parseUrlQuery(String protocolEnd) {
@@ -1052,27 +1076,35 @@ public class StringArithmetic {
     }
 
     private static String parseUrlUserInfo(String protocolEnd) {
-        int endPos = protocolEnd.indexOf('@');
+        String authority = parseUrlAuthority(protocolEnd);
+        int endPos = authority.lastIndexOf('@');
         if (endPos < 0) {
             return null;
         }
-        return protocolEnd.substring(0, endPos);
+        return authority.substring(0, endPos);
     }
 
     private static String parseUrlPort(String protocolEnd) {
-        int startPos = protocolEnd.indexOf('@');
+        String authority = parseUrlAuthority(protocolEnd);
+        int startPos = authority.lastIndexOf('@');
         startPos = startPos < 0 ? 0 : startPos + 1;
-        String hostStart = protocolEnd.substring(startPos);
-        int endPos = hostStart.indexOf(':');
-        if (endPos < 0) {
-            return null;
-        }
-        String portStart = hostStart.substring(endPos + 1);
-        int portEndPos = portStart.indexOf('/');
-        if (portEndPos < 0) {
-            portEndPos = portStart.indexOf('?');
+        String hostStart = authority.substring(startPos);
+        int portStart;
+        if (hostStart.startsWith("[")) {
+            int closeBracket = hostStart.indexOf(']');
+            if (closeBracket < 0 || closeBracket + 1 >= hostStart.length()
+                    || hostStart.charAt(closeBracket + 1) != ':') {
+                return null;
+            }
+            portStart = closeBracket + 2;
+        } else {
+            int colonPos = hostStart.indexOf(':');
+            if (colonPos < 0) {
+                return null;
+            }
+            portStart = colonPos + 1;
         }
-        return substringEnd(portStart, portEndPos);
+        return hostStart.substring(portStart);
     }
 
     /**
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/executable/StringArithmeticTest.java
 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/executable/StringArithmeticTest.java
index fda0a6f31b6..22f0292f7c9 100644
--- 
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/executable/StringArithmeticTest.java
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/expressions/functions/executable/StringArithmeticTest.java
@@ -120,6 +120,47 @@ class StringArithmeticTest {
         assertExtractUrlParameter("http://h/p?k1=aa&k2=bb#f";, "k2", "bb");
     }
 
+    @Test
+    void testParseUrlStopsAtTheAuthority() {
+        // A ':' in the path is not a port separator, and an '@' in the path 
is not a
+        // userinfo separator.
+        assertParseUrl("http://example.com/a:b";, "HOST", "example.com");
+        assertParseUrlIsNull("http://example.com/a:b";, "PORT");
+        assertParseUrl("http://example.com/a:b";, "AUTHORITY", "example.com");
+        assertParseUrl("http://example.com/a@b:c";, "HOST", "example.com");
+        assertParseUrlIsNull("http://example.com/a@b:c";, "USERINFO");
+        // A ':' in the query or the fragment is not a port separator either.
+        assertParseUrlIsNull("http://example.com/p?r=http:8080";, "PORT");
+        assertParseUrl("http://example.com#f:1";, "HOST", "example.com");
+        assertParseUrlIsNull("http://example.com#f:1";, "PORT");
+        assertParseUrl("http://example.com?x=1";, "AUTHORITY", "example.com");
+        assertParseUrl("http://[2001:db8::1]:8080/a?x=1#r";, "HOST", 
"[2001:db8::1]");
+        assertParseUrl("http://[2001:db8::1]:8080/a?x=1#r";, "PORT", "8080");
+        assertParseUrl("http://h/p#frag?x=1";, "PATH", "/p");
+        assertParseUrlIsNull("http://h/p#frag?x=1";, "QUERY");
+        // FILE includes a query even when the URL has no path, while PATH 
remains empty.
+        assertParseUrl("http://h?k=/v#frag";, "FILE", "?k=/v");
+        assertParseUrl("http://h?k=/v#frag";, "PATH", "");
+        // A real port and a real userinfo are still returned.
+        assertParseUrl("http://user:[email protected]:80/a:b";, "HOST", 
"example.com");
+        assertParseUrl("http://user:[email protected]:80/a:b";, "PORT", "80");
+        assertParseUrl("http://user:[email protected]:80/a:b";, "USERINFO", 
"user:pass");
+        assertParseUrl("http://user:[email protected]:80/a:b";, "AUTHORITY",
+                "user:[email protected]:80");
+    }
+
+    private void assertParseUrl(String url, String part, String expected) {
+        Expression result = StringArithmetic.parseurl(
+                new StringLiteral(url), new StringLiteral(part));
+        Assertions.assertEquals(expected, ((StringLikeLiteral) 
result).getValue());
+    }
+
+    private void assertParseUrlIsNull(String url, String part) {
+        Expression result = StringArithmetic.parseurl(
+                new StringLiteral(url), new StringLiteral(part));
+        Assertions.assertTrue(result instanceof NullLiteral, url + " " + part);
+    }
+
     private void assertParseUrlQuery(String url, String expected) {
         Expression result = StringArithmetic.parseurl(
                 new StringLiteral(url), new StringLiteral("QUERY"));


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to