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 ca07a0396db branch-4.2: [fix](be) Limit parse_url QUERY key lookup to
the query component #68011 (#70124)
ca07a0396db is described below
commit ca07a0396db46b4003b46fbdfd1036e14356cb3c
Author: morrySnow <[email protected]>
AuthorDate: Wed Nov 4 08:36:14 2026 +0800
branch-4.2: [fix](be) Limit parse_url QUERY key lookup to the query
component #68011 (#70124)
picked from master #68011
Co-authored-by: TengJianPing <[email protected]>
---
be/src/util/url_parser.cpp | 132 ++++++++++++---------
be/src/util/url_parser.h | 6 +
be/test/exprs/function/function_string_test.cpp | 28 ++++-
be/test/exprs/function/function_url_test.cpp | 68 +++++++++++
.../functions/executable/StringArithmetic.java | 11 ++
.../functions/executable/StringArithmeticTest.java | 38 ++++++
.../data/function_p0/test_parse_url_key.out | 43 +++++++
.../suites/function_p0/test_parse_url_key.groovy | 82 +++++++++++++
.../fold_constant_string_arithmatic.groovy | 14 +++
9 files changed, 360 insertions(+), 62 deletions(-)
diff --git a/be/src/util/url_parser.cpp b/be/src/util/url_parser.cpp
index 00ed91f52f9..292ed950876 100644
--- a/be/src/util/url_parser.cpp
+++ b/be/src/util/url_parser.cpp
@@ -21,7 +21,9 @@
#include <stdint.h>
#include <algorithm>
+#include <limits>
#include <string>
+#include <utility>
#include "core/string_ref.h"
#include "util/string_search.hpp"
@@ -50,6 +52,29 @@ const StringSearch UrlParser::_s_colon_search(&_s_colon);
const StringSearch UrlParser::_s_question_search(&_s_question);
const StringSearch UrlParser::_s_hash_search(&_s_hash);
+bool UrlParser::find_query_component(const StringRef& url, StringRef* query) {
+ // The query component starts right after the first '?'.
+ int32_t query_pos = _s_question_search.search(&url);
+ if (query_pos < 0) {
+ // Query component is missing.
+ return false;
+ }
+
+ // The first '#' bounds the query component from the right, so this single
search also
+ // provides the position of the fragment.
+ int32_t fragment_pos = _s_hash_search.search(&url);
+ if (fragment_pos >= 0 && fragment_pos < query_pos) {
+ // The '#' comes before the '?', so the '?' and everything behind it
belongs to the
+ // fragment and url has no query component.
+ return false;
+ }
+
+ int32_t query_start = query_pos + cast_set<int32_t>(_s_question.size);
+ int32_t query_end = fragment_pos >= 0 ? fragment_pos :
cast_set<int32_t>(url.size);
+ *query = url.substring(query_start, query_end - query_start);
+ return true;
+}
+
bool UrlParser::parse_url(const StringRef& url, UrlPart part, StringRef*
result) {
result->data = nullptr;
result->size = 0;
@@ -139,18 +164,15 @@ bool UrlParser::parse_url(const StringRef& url, UrlPart
part, StringRef* result)
}
case QUERY: {
- // Find first '?'.
- int32_t start_pos = _s_question_search.search(&protocol_end);
-
- if (start_pos < 0) {
+ // The query component starts at the first '?' and ends before the '#'
that starts the
+ // fragment, so a url whose first '#' comes before its first '?' has
no query.
+ StringRef query;
+ if (!find_query_component(protocol_end, &query)) {
// Indicate no query was found.
return false;
}
- StringRef query_start = protocol_end.substring(start_pos +
_s_question.size);
- // End string _s_at next '#'.
- int32_t end_pos = _s_hash_search.search(&query_start);
- *result = query_start.substring(0, end_pos);
+ *result = query;
break;
}
@@ -227,57 +249,58 @@ bool UrlParser::parse_url_key(const StringRef& url,
UrlPart part, const StringRe
// Remove leading and trailing spaces.
StringRef trimmed_url = url.trim();
- // Search for the key in the url, ignoring malformed URLs for now.
+ // The key can only be found in the query component, which starts at the
first '?' and ends
+ // before the '#' that starts the fragment (if any).
+ StringRef query;
+ if (!find_query_component(trimmed_url, &query)) {
+ // Query component is missing, the whole url is the path plus the
fragment.
+ return false;
+ }
+
+ // Search for the key inside the query component, ignoring malformed URLs
for now.
StringSearch key_search(&key);
+ // Offset of the next search inside the query component. The query
component starts right
+ // after the '?', so a key at offset 0 is a query key as well.
+ int32_t offset = 0;
- while (trimmed_url.size > 0) {
- // Search for the key in the current substring.
- int32_t key_pos = key_search.search(&trimmed_url);
- bool match = true;
+ while (std::cmp_less(offset, query.size)) {
+ // Search for the key in the remaining part of the query component.
+ StringRef rest = query.substring(offset);
+ int32_t key_pos = key_search.search(&rest);
if (key_pos < 0) {
- return false;
- }
-
- // Key pos must be != 0 because it must be preceded by a '?' or a '&'.
- // Check that the char before key_pos is either '?' or '&'.
- if (key_pos == 0 ||
- (trimmed_url.data[key_pos - 1] != '?' && trimmed_url.data[key_pos
- 1] != '&')) {
- match = false;
+ // No (more) key in the query component.
+ break;
}
- // Advance substring beyond matching key.
- trimmed_url = trimmed_url.substring(key_pos + key.size);
-
- if (!match) {
+ offset += key_pos;
+ // The key must start the query component or be preceded by a '&'.
+ if (offset != 0 && query.data[offset - 1] != '&') {
+ // The matched text is not a key, step over it and keep searching.
+ offset += cast_set<int32_t>(key.size);
continue;
}
- if (trimmed_url.size <= 0) {
- break;
- }
+ // Positioned to the char right after the key.
+ int32_t value_pos = offset + cast_set<int32_t>(key.size);
- // Next character must be '=', otherwise the match cannot be a key in
the query part.
- if (trimmed_url.data[0] != '=') {
+ // The key must be followed by a '=' and a value, otherwise the match
cannot be a key.
+ if (value_pos >= cast_set<int32_t>(query.size) ||
query.data[value_pos] != '=') {
+ // Step over the matched text and keep searching.
+ offset += cast_set<int32_t>(key.size);
continue;
}
- int32_t pos = 1;
+ ++value_pos;
- // Find ending position of key's value by matching '#' or '&'.
- while (pos < trimmed_url.size) {
- switch (trimmed_url.data[pos]) {
- case '#':
- case '&':
- *result = trimmed_url.substring(1, pos - 1);
- return true;
- }
-
- ++pos;
- }
-
- // Ending position is end of string.
- *result = trimmed_url.substring(1);
+ // Find the ending position of the key's value by matching '&'.
+ StringRef value_rest = query.substring(value_pos);
+ size_t value_end_rel_pos = value_rest.find_first_of('&');
+ int32_t value_end_pos = value_end_rel_pos ==
std::numeric_limits<size_t>::max()
+ ? cast_set<int32_t>(query.size)
+ : value_pos +
cast_set<int32_t>(value_end_rel_pos);
+ // A duplicated key keeps the behaviour of returning the first value.
+ *result = query.substring(value_pos, value_end_pos - value_pos);
return true;
}
@@ -360,23 +383,15 @@ StringRef UrlParser::extract_url(StringRef url, StringRef
name) {
StringRef result("", 0);
// Remove leading and trailing spaces.
StringRef trimmed_url = url.trim();
- // find '?'
- int32_t question_pos = _s_question_search.search(&trimmed_url);
- if (question_pos < 0) {
+ // The parameters can only be found in the query component, which starts
at the first '?'
+ // and ends before the '#' that starts the fragment (if any).
+ StringRef sub_url;
+ if (!find_query_component(trimmed_url, &sub_url)) {
// this url no parameters.
// Example: https://doris.apache.org/
return result;
}
- // find '#'
- int32_t hash_pos = _s_hash_search.search(&trimmed_url);
- StringRef sub_url;
- if (hash_pos < 0) {
- sub_url = trimmed_url.substring(question_pos + 1, trimmed_url.size -
question_pos - 1);
- } else {
- sub_url = trimmed_url.substring(question_pos + 1, hash_pos -
question_pos - 1);
- }
-
// find '&' and '=', and extract target parameter
// Example: k1=aa&k2=bb&k3=cc&test=dd
int64_t and_pod;
@@ -391,8 +406,7 @@ StringRef UrlParser::extract_url(StringRef url, StringRef
name) {
key_url = sub_url.substring(0, and_pod);
sub_url = sub_url.substring(and_pod + 1, len - and_pod - 1);
} else {
- auto end_pos = sub_url.find_first_of('#');
- key_url = end_pos == -1 ? sub_url : sub_url.substring(0, end_pos);
+ key_url = sub_url;
sub_url = result;
}
len = sub_url.size;
diff --git a/be/src/util/url_parser.h b/be/src/util/url_parser.h
index df1182c6651..790a5c536cb 100644
--- a/be/src/util/url_parser.h
+++ b/be/src/util/url_parser.h
@@ -66,6 +66,12 @@ public:
static StringRef extract_url(StringRef url, StringRef name);
private:
+ // Locates the query component of url and stores it in query. The query
component starts
+ // right after the first '?' and ends before the '#' that starts the
fragment (if any).
+ // Returns false when url has no query component, which is also the case
when the first
+ // '#' comes before the first '?' because the '?' then belongs to the
fragment.
+ static bool find_query_component(const StringRef& url, StringRef* query);
+
// 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_string_test.cpp
b/be/test/exprs/function/function_string_test.cpp
index afb9bbbaabf..7805e08eb16 100644
--- a/be/test/exprs/function/function_string_test.cpp
+++ b/be/test/exprs/function/function_string_test.cpp
@@ -2696,7 +2696,14 @@ TEST(function_string_test,
function_extract_url_parameter_test) {
{{VARCHAR("http://doris.apache.org?k1=aa&k2=bb&test=dd#999/"),
VARCHAR("k3")},
{VARCHAR("")}},
{{VARCHAR("http://doris.apache.org?k1=aa&k2=bb&test=dd#999/"),
VARCHAR("test")},
- {VARCHAR("dd")}}};
+ {VARCHAR("dd")}},
+ // The first '#' comes before the first '?', so the '?' belongs to
the fragment and
+ // the url has no parameters.
+ {{VARCHAR("http://doris.apache.org#f?k1=aa"), VARCHAR("k1")},
{VARCHAR("")}},
+ {{VARCHAR("http://doris.apache.org#f?k1=aa"), VARCHAR("aa")},
{VARCHAR("")}},
+ // The parameters end before the fragment.
+ {{VARCHAR("http://doris.apache.org?k1=aa#f?k2=bb"),
VARCHAR("k1")}, {VARCHAR("aa")}},
+ {{VARCHAR("http://doris.apache.org?k1=aa#f?k2=bb"),
VARCHAR("k2")}, {VARCHAR("")}}};
check_function_all_arg_comb<DataTypeString, true>(func_name, input_types,
data_set);
}
@@ -2746,7 +2753,14 @@ TEST(function_string_test, function_parse_url_test) {
"https://www.facebook.com/aa/bb?returnpage=https://www.facebook.com/"),
std::string("HosT")},
std::string("www.facebook.com")},
- {{std::string("http://www.baidu.com"), std::string("FILE")},
{std::string("")}}};
+ {{std::string("http://www.baidu.com"), std::string("FILE")},
{std::string("")}},
+ // The first '#' comes before the first '?', so the '?'
belongs to the fragment
+ // and the url has no query component.
+ {{std::string("http://h/p#f?k=v"), std::string("QUERY")},
{Null()}},
+ {{std::string("http://h/p#f/?#k=v"), std::string("QUERY")},
{Null()}},
+ // The query component ends before the fragment.
+ {{std::string("http://h/p?k=1#f&k=2"), std::string("QUERY")},
{std::string("k=1")}},
+ {{std::string("http://h/p?"), std::string("QUERY")},
{std::string("")}}};
check_function_all_arg_comb<DataTypeString, true>(func_name,
input_types, data_set);
}
@@ -2765,7 +2779,15 @@ TEST(function_string_test, function_parse_url_test) {
{Null()}},
{{std::string("http://fb.com/path/p1.p?q=1#f"),
std::string("HOST"),
std::string("q")},
- {Null()}}};
+ {Null()}},
+ // The only '?' is inside the fragment, so the url has no
query component.
+ {{std::string("http://h/p#f?k=v"), std::string("QUERY"),
std::string("k")},
+ {Null()}},
+ // A duplicated key returns the first value.
+ {{std::string("http://h/p?k=1&k=2#f"), std::string("QUERY"),
std::string("k")},
+ {std::string("1")}},
+ {{std::string("http://h/p?k=1&k=2&k=3"), std::string("QUERY"),
std::string("k")},
+ {std::string("1")}}};
check_function_all_arg_comb<DataTypeString, true>(func_name,
input_types, data_set);
}
diff --git a/be/test/exprs/function/function_url_test.cpp
b/be/test/exprs/function/function_url_test.cpp
index 36f8b7c3457..7fc20fb104c 100644
--- a/be/test/exprs/function/function_url_test.cpp
+++ b/be/test/exprs/function/function_url_test.cpp
@@ -93,4 +93,72 @@ TEST(FunctionUrlTEST, ProtocolTest) {
static_cast<void>(check_function<DataTypeString, true>(func_name,
input_types, data_set));
}
+TEST(FunctionUrlTEST, ParseUrlQueryKeyTest) {
+ std::string func_name = "parse_url";
+ InputTypeSet input_types = {PrimitiveType::TYPE_VARCHAR,
PrimitiveType::TYPE_VARCHAR,
+ PrimitiveType::TYPE_VARCHAR};
+
+ DataSet data_set = {
+ // 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.
+ {{STRING("http://h/p#f/?#k=v"), STRING("QUERY"), STRING("k")},
Null()},
+ {{STRING("http://h/p#?k=v"), STRING("QUERY"), STRING("k")},
Null()},
+ // The url has no query component at all.
+ {{STRING("http://h/p&k=v"), STRING("QUERY"), STRING("k")}, Null()},
+ // The key only exists in the path.
+ {{STRING("http://h/p&k=v?x=1"), STRING("QUERY"), STRING("k")},
Null()},
+ // The query component of this url is 'x=1'.
+ {{STRING("http://h/p&k=v?x=1"), STRING("QUERY"), STRING("x")},
STRING("1")},
+ // The key only exists in the fragment.
+ {{STRING("http://h/p?x=1#f&k=v"), STRING("QUERY"), STRING("k")},
Null()},
+ // The key exists in the path, in the query and in the fragment.
+ {{STRING("http://h/p&k=v?k=1#f&k=2"), STRING("QUERY"),
STRING("k")}, STRING("1")},
+ {{STRING("http://h/p?a=1&k=2"), STRING("QUERY"), STRING("k")},
STRING("2")},
+ // A duplicated key keeps the behaviour of returning the first
value.
+ {{STRING("http://h/p?k=1&k=2#f"), STRING("QUERY"), STRING("k")},
STRING("1")},
+ {{STRING("http://h/p?k=1&k=2&k=3"), STRING("QUERY"), STRING("k")},
STRING("1")},
+ // Only the query component is searched, so the duplicated key in
the fragment is
+ // not part of the result.
+ {{STRING("http://h/p?k=1#k=2&k=3"), STRING("QUERY"), STRING("k")},
STRING("1")},
+ // A key without any '=' is not a valid query parameter.
+ {{STRING("http://h/p?k"), STRING("QUERY"), STRING("k")}, Null()},
+ {{STRING("http://h/p?"), STRING("QUERY"), STRING("k")}, Null()},
+ // The key is the first query parameter.
+ {{STRING("http://h/p?k=1"), STRING("QUERY"), STRING("k")},
STRING("1")},
+ {{STRING(" http://h/p?k=1 "), STRING("QUERY"), STRING("k")},
STRING("1")},
+ {{STRING(" http://h/p?sk=0&k=1"), STRING("QUERY"), STRING("k")},
STRING("1")},
+ {{STRING(" http://h/p?k&k=1"), STRING("QUERY"), STRING("k")},
STRING("1")},
+ {{STRING(" http://h/p?k=&k=1"), STRING("QUERY"), STRING("k")},
STRING("")},
+ {{STRING("http://h/p?k=1"), STRING("HOST"), STRING("k")}, Null()},
+ };
+
+ static_cast<void>(check_function<DataTypeString, true>(func_name,
input_types, data_set));
+}
+
+TEST(FunctionUrlTEST, ParseUrlQueryTest) {
+ std::string func_name = "parse_url";
+ InputTypeSet input_types = {PrimitiveType::TYPE_VARCHAR,
PrimitiveType::TYPE_VARCHAR};
+
+ DataSet data_set = {
+ // The only '?' is inside the fragment, so the url has no query
component. The
+ // keyed form of parse_url must report the same.
+ {{STRING("http://h/p#f?k=v"), STRING("QUERY")}, Null()},
+ {{STRING("http://h/p#f/?#k=v"), STRING("QUERY")}, Null()},
+ {{STRING("http://h/p#?k=v"), STRING("QUERY")}, Null()},
+ // The url has no query component at all.
+ {{STRING("http://h/p&k=v"), STRING("QUERY")}, Null()},
+ {{STRING("http://h/p"), STRING("QUERY")}, Null()},
+ // The query component starts at the first '?' and ends before the
fragment.
+ {{STRING("http://h/p?k=1"), STRING("QUERY")}, STRING("k=1")},
+ {{STRING("http://h/p?k=1#f&k=2"), STRING("QUERY")}, STRING("k=1")},
+ {{STRING("http://h/p?a=1&k=2"), STRING("QUERY")},
STRING("a=1&k=2")},
+ // An empty query component is not NULL.
+ {{STRING("http://h/p?"), STRING("QUERY")}, STRING("")},
+ {{STRING(" http://h/p?k=1 "), STRING("QUERY")}, STRING("k=1")},
+ };
+
+ 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 db0742117fa..3a422fda284 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
@@ -1033,6 +1033,12 @@ public class StringArithmetic {
if (startPos < 0) {
return null;
}
+ int fragmentPos = protocolEnd.indexOf('#');
+ if (fragmentPos >= 0 && fragmentPos < startPos) {
+ // The '#' comes before the '?', so the '?' and everything behind
it belongs to the
+ // fragment and the url has no query component.
+ return null;
+ }
String queryStart = protocolEnd.substring(startPos + 1);
return substringEnd(queryStart, queryStart.indexOf('#'));
}
@@ -1181,6 +1187,11 @@ public class StringArithmetic {
return castStringLikeLiteral(first, "");
}
int hashPos = trimmedUrl.indexOf('#');
+ if (hashPos >= 0 && hashPos < questionPos) {
+ // The '#' comes before the '?', so the '?' and everything behind
it belongs to the
+ // fragment and the url has no query parameters.
+ return castStringLikeLiteral(first, "");
+ }
String subUrl = hashPos < 0
? trimmedUrl.substring(questionPos + 1)
: trimmedUrl.substring(questionPos + 1, hashPos);
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 5381bc8e062..fda0a6f31b6 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
@@ -23,6 +23,7 @@ import
org.apache.doris.nereids.trees.expressions.functions.scalar.UrlDecode;
import org.apache.doris.nereids.trees.expressions.literal.DoubleLiteral;
import org.apache.doris.nereids.trees.expressions.literal.FloatLiteral;
import org.apache.doris.nereids.trees.expressions.literal.IntegerLiteral;
+import org.apache.doris.nereids.trees.expressions.literal.NullLiteral;
import org.apache.doris.nereids.trees.expressions.literal.StringLikeLiteral;
import org.apache.doris.nereids.trees.expressions.literal.StringLiteral;
import org.apache.doris.nereids.trees.expressions.literal.TimeStampNsLiteral;
@@ -99,4 +100,41 @@ class StringArithmeticTest {
Expression result = ExpressionEvaluator.INSTANCE.eval(new
UrlDecode(new StringLiteral(encoded)));
Assertions.assertEquals(expected, ((StringLikeLiteral)
result).getValue());
}
+
+ @Test
+ void testParseUrlQueryStopsAtFragment() {
+ // The only '?' is inside the fragment, so the url has no query
component.
+ assertParseUrlQueryIsNull("http://h/p#f?k=v");
+ assertParseUrlQueryIsNull("http://h/p#f/?#k=v");
+ // The query component starts at the first '?' and ends before the
fragment.
+ assertParseUrlQuery("http://h/p?k=1#f&k=2", "k=1");
+ assertParseUrlQuery("http://h/p?a=1&k=2", "a=1&k=2");
+ }
+
+ @Test
+ void testExtractUrlParameterStopsAtFragment() {
+ // The only '?' is inside the fragment, so the url has no parameters.
+ assertExtractUrlParameter("http://h/p#f?k=v", "k", "");
+ // The parameters end before the fragment.
+ assertExtractUrlParameter("http://h/p?k=1#f&k=2", "k", "1");
+ assertExtractUrlParameter("http://h/p?k1=aa&k2=bb#f", "k2", "bb");
+ }
+
+ private void assertParseUrlQuery(String url, String expected) {
+ Expression result = StringArithmetic.parseurl(
+ new StringLiteral(url), new StringLiteral("QUERY"));
+ Assertions.assertEquals(expected, ((StringLikeLiteral)
result).getValue());
+ }
+
+ private void assertParseUrlQueryIsNull(String url) {
+ Expression result = StringArithmetic.parseurl(
+ new StringLiteral(url), new StringLiteral("QUERY"));
+ Assertions.assertTrue(result instanceof NullLiteral, url);
+ }
+
+ private void assertExtractUrlParameter(String url, String parameter,
String expected) {
+ Expression result = StringArithmetic.extractUrlParameter(
+ new StringLiteral(url), new StringLiteral(parameter));
+ Assertions.assertEquals(expected, ((StringLikeLiteral)
result).getValue());
+ }
}
diff --git a/regression-test/data/function_p0/test_parse_url_key.out
b/regression-test/data/function_p0/test_parse_url_key.out
new file mode 100644
index 00000000000..69e7ac35fb0
--- /dev/null
+++ b/regression-test/data/function_p0/test_parse_url_key.out
@@ -0,0 +1,43 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !sql --
+1 \N
+2 x=1
+3 \N
+4 x=1
+5 k=1
+6 \N
+7 \N
+8 a=1&k=2
+9 k=1&k=2
+
+-- !sql --
+1 \N
+2 \N
+3 \N
+4 \N
+5 1
+6 \N
+7 \N
+8 2
+9 1
+
+-- !sql --
+1 \N
+2 1
+3 \N
+4 1
+5 \N
+6 \N
+7 \N
+8 \N
+9 \N
+
+-- !sql --
+9 1
+
+-- !sql --
+\N \N k=1 1
+
+-- !sql --
+ 2
+
diff --git a/regression-test/suites/function_p0/test_parse_url_key.groovy
b/regression-test/suites/function_p0/test_parse_url_key.groovy
new file mode 100644
index 00000000000..139a8dd0d1f
--- /dev/null
+++ b/regression-test/suites/function_p0/test_parse_url_key.groovy
@@ -0,0 +1,82 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+suite("test_parse_url_key") {
+
+ sql """
+ drop table if exists test_parse_url_key;
+ """
+
+ sql """
+ CREATE TABLE `test_parse_url_key` (
+ `id` int NULL,
+ `url` text NULL
+ ) ENGINE=OLAP
+ DUPLICATE KEY(`id`)
+ DISTRIBUTED BY RANDOM BUCKETS AUTO
+ PROPERTIES (
+ "replication_allocation" = "tag.location.default: 1"
+ );
+ """
+
+ sql """
+ insert into test_parse_url_key values
+ (1, 'http://h/p#f?k=v'),
+ (2, 'http://h/p&k=v?x=1'),
+ (3, 'http://h/p#f/?#k=v'),
+ (4, 'http://h/p?x=1#f&k=v'),
+ (5, 'http://h/p&k=v?k=1#f&k=2'),
+ (6, 'http://h/p#f&k=v?k=2'),
+ (7, 'http://h/p'),
+ (8, 'http://h/p?a=1&k=2'),
+ (9, 'http://h/p?k=1&k=2#f')
+ """
+
+ // The query component is located between the first '?' and the following
'#'. Keys
+ // appearing in the path or in the fragment must not be returned, and a
url whose '#'
+ // comes before its '?' has no query component at all.
+ qt_sql """
+ select id, parse_url(url, 'QUERY') as query from test_parse_url_key
order by id
+ """
+
+ qt_sql """
+ select id, parse_url(url, 'QUERY', 'k') as query_k from
test_parse_url_key order by id
+ """
+
+ qt_sql """
+ select id, parse_url(url, 'QUERY', 'x') as query_x from
test_parse_url_key order by id
+ """
+
+ // A duplicated key returns its first value.
+ qt_sql """
+ select id, parse_url(url, 'QUERY', 'k') as first_k from
test_parse_url_key where id = 9
+ """
+
+ // The constant folding of the same urls must agree with the runtime
evaluation.
+ qt_sql """
+ select parse_url('http://h/p#f?k=v', 'QUERY'),
+ parse_url('http://h/p#f?k=v', 'QUERY', 'k'),
+ parse_url('http://h/p?k=1#f&k=2', 'QUERY'),
+ parse_url('http://h/p?k=1&k=2#f', 'QUERY', 'k')
+ """
+
+ // extract_url_parameter has to bound the parameters by the query
component as well.
+ qt_sql """
+ select extract_url_parameter('http://h/p#f?k=v', 'k'),
+ extract_url_parameter('http://h/p?a=1&k=2#f', 'k')
+ """
+}
diff --git
a/regression-test/suites/nereids_p0/expression/fold_constant/fold_constant_string_arithmatic.groovy
b/regression-test/suites/nereids_p0/expression/fold_constant/fold_constant_string_arithmatic.groovy
index 2d4ccdbddff..f7f590c28ec 100644
---
a/regression-test/suites/nereids_p0/expression/fold_constant/fold_constant_string_arithmatic.groovy
+++
b/regression-test/suites/nereids_p0/expression/fold_constant/fold_constant_string_arithmatic.groovy
@@ -631,6 +631,13 @@ suite("fold_constant_string_arithmatic") {
testFoldConst("select parse_url('http://www.example.com/path?query=こんにちは',
'QUERY')")
testFoldConst("select
parse_url(\"http://www.example.com/path?query=a\b\'\", 'QUERY')")
testFoldConst("select
parse_url(\"http://www.example.com/path.query=a\b\'\", 'QUERY')")
+ // The query component is located between the first '?' and the fragment,
so a url whose
+ // '#' comes before its '?' has no query component.
+ testFoldConst("select parse_url('http://h/p#f?k=v', 'QUERY')")
+ testFoldConst("select parse_url('http://h/p#f/?#k=v', 'QUERY')")
+ // The query component ends before the fragment.
+ testFoldConst("select parse_url('http://h/p?k=1#f&k=2', 'QUERY')")
+ testFoldConst("select parse_url('http://h/p?k=1&k=2#f', 'QUERY')")
testFoldConst("select PARSE_URL('http://example.com', 'PROTOCOL')")
testFoldConst("select PARSE_URL('http://example.com', 'protocol')")
testFoldConst("select PARSE_URL('http://example.com', 'Protocol')")
@@ -1502,6 +1509,13 @@ suite("fold_constant_string_arithmatic") {
testFoldConst("select
extract_url_parameter('http://user:[email protected]?a=b', null)")
testFoldConst("select extract_url_parameter(null, 'a')")
testFoldConst("select
extract_url_parameter('http://user:[email protected]?a=b', 'a&b')")
+ // The parameters are located between the first '?' and the fragment, so a
url whose '#'
+ // comes before its '?' has no parameters.
+ testFoldConst("select extract_url_parameter('http://h/p#f?k=v', 'k')")
+ testFoldConst("select extract_url_parameter('http://h/p#f?k=v', 'v')")
+ // The parameters end before the fragment.
+ testFoldConst("select extract_url_parameter('http://h/p?a=1&k=2#f', 'k')")
+ testFoldConst("select extract_url_parameter('http://h/p?a=1#f&k=2', 'k')")
testFoldConst("select
extract_url_parameter('http://user:[email protected]?a=b&c=d', 'c')")
testFoldConst("select
extract_url_parameter('http://user:[email protected]?a=b&c=d', 'C')")
testFoldConst("select
extract_url_parameter('http://user:[email protected]?a=b&c=d', 'd')")
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]