dylanhz commented on code in PR #27351:
URL: https://github.com/apache/flink/pull/27351#discussion_r2919431586


##########
flink-table/flink-table-api-java/src/main/java/org/apache/flink/table/api/internal/BaseExpressions.java:
##########
@@ -1401,15 +1402,37 @@ public OutType locate(InType str, InType pos) {
     }
 
     /**
-     * Decodes a given string in 'application/x-www-form-urlencoded' format 
using the UTF-8 encoding
-     * scheme. If the input is null, or there is an issue with the decoding 
process(such as
-     * encountering an illegal escape pattern), or the encoding scheme is not 
supported, will return
-     * null.
+     * Translates a string from 'application/x-www-form-urlencoded' format 
using the UTF-8 encoding
+     * scheme. If the input is null, or there is an issue with the decoding 
process, or the encoding
+     * scheme is not supported, will return null.
      */
     public OutType urlDecode() {
         return toApiSpecificExpression(unresolvedCall(URL_DECODE, toExpr()));
     }
 
+    /**
+     * Recursively decodes a URL-encoded string from 
'application/x-www-form-urlencoded' format
+     * using the UTF-8 encoding scheme until no further decoding is possible 
or the default max
+     * depth of 10 is reached. If the input is null, or there is an issue with 
the decoding process,
+     * or the encoding scheme is not supported, will return null.

Review Comment:
   ```suggestion
        * or the encoding scheme is not supported, returns null.
   ```



##########
flink-table/flink-table-api-java/src/main/java/org/apache/flink/table/api/internal/BaseExpressions.java:
##########
@@ -1401,15 +1402,37 @@ public OutType locate(InType str, InType pos) {
     }
 
     /**
-     * Decodes a given string in 'application/x-www-form-urlencoded' format 
using the UTF-8 encoding
-     * scheme. If the input is null, or there is an issue with the decoding 
process(such as
-     * encountering an illegal escape pattern), or the encoding scheme is not 
supported, will return
-     * null.
+     * Translates a string from 'application/x-www-form-urlencoded' format 
using the UTF-8 encoding
+     * scheme. If the input is null, or there is an issue with the decoding 
process, or the encoding
+     * scheme is not supported, will return null.

Review Comment:
   ```suggestion
        * scheme is not supported, returns null.
   ```



##########
docs/data/sql_functions.yml:
##########
@@ -492,6 +492,11 @@ string:
     description:
       Decodes a given string in 'application/x-www-form-urlencoded' format 
using the UTF-8 encoding scheme.
       If the input is NULL, or there is an issue with the decoding 
process(such as encountering an illegal escape pattern), or the encoding scheme 
is not supported, the function returns NULL.
+  - sql: URL_DECODE_RECURSIVE(string[, integer])
+    table: STRING.urlDecodeRecursive() / STRING.urlDecodeRecursive_n(INTEGER)

Review Comment:
   ```suggestion
       table: STRING.urlDecodeRecursive() / STRING.urlDecodeRecursive(INTEGER)
   ```



##########
flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/functions/scalar/UrlDecodeFunction.java:
##########
@@ -30,22 +31,78 @@
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 
-/** Implementation of {@link BuiltInFunctionDefinitions#URL_DECODE}. */
+/**
+ * Implementation of {@link BuiltInFunctionDefinitions#URL_DECODE} and {@link
+ * BuiltInFunctionDefinitions#URL_DECODE_RECURSIVE}.
+ */
 @Internal
 public class UrlDecodeFunction extends BuiltInScalarFunction {
 
+    private static final int DEFAULT_MAX_DECODE_DEPTH = 10;
+
+    private final boolean recursive;
+
     public UrlDecodeFunction(SpecializedFunction.SpecializedContext context) {
-        super(BuiltInFunctionDefinitions.URL_DECODE, context);
+        super(resolveDefinition(context), context);
+        this.recursive =
+                resolveDefinition(context) == 
BuiltInFunctionDefinitions.URL_DECODE_RECURSIVE;
+    }
+
+    private static BuiltInFunctionDefinition resolveDefinition(
+            SpecializedFunction.SpecializedContext context) {
+        if (context.getCallContext()
+                .getFunctionDefinition()
+                .equals(BuiltInFunctionDefinitions.URL_DECODE_RECURSIVE)) {

Review Comment:
   nit: Use `==` rather than `equals()` as in line 48.



##########
flink-table/flink-table-runtime/src/test/java/org/apache/flink/table/runtime/functions/scalar/UrlDecodeFunctionTest.java:
##########
@@ -0,0 +1,297 @@
+/*
+ * 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.
+ */
+
+package org.apache.flink.table.runtime.functions.scalar;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.ReadableConfig;
+import org.apache.flink.table.api.DataTypes;
+import org.apache.flink.table.catalog.DataTypeFactory;
+import org.apache.flink.table.data.StringData;
+import org.apache.flink.table.expressions.Expression;
+import org.apache.flink.table.functions.BuiltInFunctionDefinition;
+import org.apache.flink.table.functions.BuiltInFunctionDefinitions;
+import org.apache.flink.table.functions.FunctionDefinition;
+import org.apache.flink.table.functions.SpecializedFunction;
+import org.apache.flink.table.types.DataType;
+import org.apache.flink.table.types.inference.CallContext;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** Tests for {@link UrlDecodeFunction}. */
+class UrlDecodeFunctionTest {
+
+    private UrlDecodeFunction createFunction(boolean recursive) {
+        BuiltInFunctionDefinition definition =
+                recursive
+                        ? BuiltInFunctionDefinitions.URL_DECODE_RECURSIVE
+                        : BuiltInFunctionDefinitions.URL_DECODE;
+
+        CallContext callContext =
+                new CallContext() {
+                    @Override
+                    public DataTypeFactory getDataTypeFactory() {
+                        throw new UnsupportedOperationException();
+                    }
+
+                    @Override
+                    public FunctionDefinition getFunctionDefinition() {
+                        return definition;
+                    }
+
+                    @Override
+                    public boolean isArgumentLiteral(int pos) {
+                        return false;
+                    }
+
+                    @Override
+                    public boolean isArgumentNull(int pos) {
+                        return false;
+                    }
+
+                    @Override
+                    public <T> java.util.Optional<T> getArgumentValue(int pos, 
Class<T> clazz) {
+                        return java.util.Optional.empty();
+                    }
+
+                    @Override
+                    public List<DataType> getArgumentDataTypes() {
+                        return Collections.singletonList(DataTypes.STRING());
+                    }
+
+                    @Override
+                    public Optional<DataType> getOutputDataType() {
+                        return Optional.of(DataTypes.STRING().nullable());
+                    }
+
+                    @Override
+                    public String getName() {
+                        return definition.getName();
+                    }
+
+                    @Override
+                    public boolean isGroupedAggregation() {
+                        return false;
+                    }
+                };
+
+        SpecializedFunction.SpecializedContext context =
+                new SpecializedFunction.SpecializedContext() {
+                    @Override
+                    public CallContext getCallContext() {
+                        return callContext;
+                    }
+
+                    @Override
+                    public ReadableConfig getConfiguration() {
+                        return new Configuration();
+                    }
+
+                    @Override
+                    public ClassLoader getBuiltInClassLoader() {
+                        return Thread.currentThread().getContextClassLoader();
+                    }
+
+                    @Override
+                    public SpecializedFunction.ExpressionEvaluator 
createEvaluator(
+                            Expression expression,
+                            DataType outputDataType,
+                            DataTypes.Field... args) {
+                        throw new UnsupportedOperationException();
+                    }
+
+                    @Override
+                    public SpecializedFunction.ExpressionEvaluator 
createEvaluator(
+                            String sqlExpression,
+                            DataType outputDataType,
+                            DataTypes.Field... args) {
+                        throw new UnsupportedOperationException();
+                    }
+
+                    @Override
+                    public SpecializedFunction.ExpressionEvaluator 
createEvaluator(
+                            BuiltInFunctionDefinition function,
+                            DataType outputDataType,
+                            DataType... args) {
+                        throw new UnsupportedOperationException();
+                    }
+                };
+        return new UrlDecodeFunction(context);
+    }
+
+    // === URL_DECODE (non-recursive) tests ===
+
+    @Test
+    void testUrlDecodeBasic() {

Review Comment:
   nit: I'd prefer to keep only IT cases if they are exactly the same as UT 
cases.



##########
flink-table/flink-table-runtime/src/test/java/org/apache/flink/table/runtime/functions/scalar/UrlDecodeFunctionTest.java:
##########
@@ -0,0 +1,297 @@
+/*
+ * 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.
+ */
+
+package org.apache.flink.table.runtime.functions.scalar;
+
+import org.apache.flink.configuration.Configuration;
+import org.apache.flink.configuration.ReadableConfig;
+import org.apache.flink.table.api.DataTypes;
+import org.apache.flink.table.catalog.DataTypeFactory;
+import org.apache.flink.table.data.StringData;
+import org.apache.flink.table.expressions.Expression;
+import org.apache.flink.table.functions.BuiltInFunctionDefinition;
+import org.apache.flink.table.functions.BuiltInFunctionDefinitions;
+import org.apache.flink.table.functions.FunctionDefinition;
+import org.apache.flink.table.functions.SpecializedFunction;
+import org.apache.flink.table.types.DataType;
+import org.apache.flink.table.types.inference.CallContext;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/** Tests for {@link UrlDecodeFunction}. */
+class UrlDecodeFunctionTest {
+
+    private UrlDecodeFunction createFunction(boolean recursive) {
+        BuiltInFunctionDefinition definition =
+                recursive
+                        ? BuiltInFunctionDefinitions.URL_DECODE_RECURSIVE
+                        : BuiltInFunctionDefinitions.URL_DECODE;
+
+        CallContext callContext =

Review Comment:
   nit: We can use `CallContextMock` to eliminate unnecessary codes.



##########
flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/functions/scalar/UrlDecodeFunction.java:
##########
@@ -30,22 +31,78 @@
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 
-/** Implementation of {@link BuiltInFunctionDefinitions#URL_DECODE}. */
+/**
+ * Implementation of {@link BuiltInFunctionDefinitions#URL_DECODE} and {@link
+ * BuiltInFunctionDefinitions#URL_DECODE_RECURSIVE}.
+ */
 @Internal
 public class UrlDecodeFunction extends BuiltInScalarFunction {
 
+    private static final int DEFAULT_MAX_DECODE_DEPTH = 10;
+
+    private final boolean recursive;
+
     public UrlDecodeFunction(SpecializedFunction.SpecializedContext context) {
-        super(BuiltInFunctionDefinitions.URL_DECODE, context);
+        super(resolveDefinition(context), context);
+        this.recursive =
+                resolveDefinition(context) == 
BuiltInFunctionDefinitions.URL_DECODE_RECURSIVE;
+    }
+
+    private static BuiltInFunctionDefinition resolveDefinition(
+            SpecializedFunction.SpecializedContext context) {
+        if (context.getCallContext()
+                .getFunctionDefinition()
+                .equals(BuiltInFunctionDefinitions.URL_DECODE_RECURSIVE)) {
+            return BuiltInFunctionDefinitions.URL_DECODE_RECURSIVE;
+        }
+        return BuiltInFunctionDefinitions.URL_DECODE;
     }
 
     public @Nullable StringData eval(StringData value) {
         if (value == null) {
             return null;
         }
         final Charset charset = StandardCharsets.UTF_8;
+        if (!recursive) {
+            try {
+                return 
StringData.fromString(URLDecoder.decode(value.toString(), charset.name()));
+            } catch (UnsupportedEncodingException | RuntimeException e) {
+                return null;
+            }
+        }
+
+        // Recursive with default max depth
+        return eval(value, DEFAULT_MAX_DECODE_DEPTH);
+    }
+
+    public @Nullable StringData eval(StringData value, Integer maxDepth) {
+        if (value == null) {
+            return null;
+        }
+        if (maxDepth == null || maxDepth <= 0) {
+            maxDepth = DEFAULT_MAX_DECODE_DEPTH;
+        }
+        final Charset charset = StandardCharsets.UTF_8;
+
+        String currentValue = value.toString();
+        String previousValue = currentValue;
+        int iteration = 0;
+
         try {
-            return StringData.fromString(URLDecoder.decode(value.toString(), 
charset.name()));
+            while (iteration < maxDepth) {
+                previousValue = currentValue;
+                currentValue = URLDecoder.decode(currentValue, charset.name());

Review Comment:
   nit: We can use `URLDecoder.decode(String, Charset)` since JDK 8 is no 
longer supported, and remove `UnsupportedEncodingException` catching as well.



##########
flink-table/flink-table-common/src/main/java/org/apache/flink/table/functions/BuiltInFunctionDefinitions.java:
##########
@@ -453,6 +453,21 @@ ANY, and(logical(LogicalTypeRoot.BOOLEAN), LITERAL)
                             
"org.apache.flink.table.runtime.functions.scalar.UrlDecodeFunction")
                     .build();
 
+    public static final BuiltInFunctionDefinition URL_DECODE_RECURSIVE =
+            BuiltInFunctionDefinition.newBuilder()
+                    .name("URL_DECODE_RECURSIVE")
+                    .kind(SCALAR)
+                    .inputTypeStrategy(
+                            or(
+                                    
sequence(logical(LogicalTypeFamily.CHARACTER_STRING)),
+                                    sequence(
+                                            
logical(LogicalTypeFamily.CHARACTER_STRING),
+                                            
logical(LogicalTypeFamily.INTEGER_NUMERIC))))

Review Comment:
   Should `max_depth` be a literal? It's a configuration-like parameter that 
shouldn't vary per row.



##########
docs/data/sql_functions_zh.yml:
##########
@@ -585,7 +585,14 @@ string:
   - sql: URL_DECODE(string)
     table: STRING.urlDecode()
     description:
-      使用 UTF-8 编码方案对“application/x-www-form-urlencoded”格式的给定字符串进行解码。
+      使用 UTF-8 编码方案对"application/x-www-form-urlencoded"格式的给定字符串进行解码。
+      如果输入为 NULL,或者解码过程出现问题(例如遇到非法转义模式或者不支持该编码方案),该函数将返回 NULL。
+  - sql: URL_DECODE_RECURSIVE(string, boolean)
+    table: STRING.urlDecode(BOOLEAN)
+    description:
+      使用 UTF-8 编码方案对"application/x-www-form-urlencoded"格式的给定字符串进行解码,支持可选递归解码。
+      如果 recursive 参数为 TRUE,则执行递归解码直到无法进一步解码为止。
+      如果 recursive 参数为 FALSE 或 NULL,则仅执行单次解码操作。

Review Comment:
   Please update Chinese doc accordingly.



##########
flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/functions/scalar/UrlDecodeFunction.java:
##########
@@ -30,22 +31,78 @@
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 
-/** Implementation of {@link BuiltInFunctionDefinitions#URL_DECODE}. */
+/**
+ * Implementation of {@link BuiltInFunctionDefinitions#URL_DECODE} and {@link
+ * BuiltInFunctionDefinitions#URL_DECODE_RECURSIVE}.
+ */
 @Internal
 public class UrlDecodeFunction extends BuiltInScalarFunction {
 
+    private static final int DEFAULT_MAX_DECODE_DEPTH = 10;
+
+    private final boolean recursive;
+
     public UrlDecodeFunction(SpecializedFunction.SpecializedContext context) {
-        super(BuiltInFunctionDefinitions.URL_DECODE, context);
+        super(resolveDefinition(context), context);
+        this.recursive =
+                resolveDefinition(context) == 
BuiltInFunctionDefinitions.URL_DECODE_RECURSIVE;
+    }
+
+    private static BuiltInFunctionDefinition resolveDefinition(
+            SpecializedFunction.SpecializedContext context) {
+        if (context.getCallContext()
+                .getFunctionDefinition()
+                .equals(BuiltInFunctionDefinitions.URL_DECODE_RECURSIVE)) {
+            return BuiltInFunctionDefinitions.URL_DECODE_RECURSIVE;
+        }
+        return BuiltInFunctionDefinitions.URL_DECODE;
     }
 
     public @Nullable StringData eval(StringData value) {
         if (value == null) {
             return null;
         }
         final Charset charset = StandardCharsets.UTF_8;
+        if (!recursive) {
+            try {
+                return 
StringData.fromString(URLDecoder.decode(value.toString(), charset.name()));
+            } catch (UnsupportedEncodingException | RuntimeException e) {
+                return null;
+            }
+        }
+
+        // Recursive with default max depth
+        return eval(value, DEFAULT_MAX_DECODE_DEPTH);
+    }
+
+    public @Nullable StringData eval(StringData value, Integer maxDepth) {
+        if (value == null) {
+            return null;
+        }
+        if (maxDepth == null || maxDepth <= 0) {

Review Comment:
   As I mentioned in the definition comment, can we move this validation to the 
planning stage if it is required to be a literal?



##########
flink-table/flink-table-planner/src/test/java/org/apache/flink/table/planner/functions/UrlFunctionsITCase.java:
##########
@@ -59,6 +59,66 @@ Stream<TestSetSpec> getTestSetSpecs() {
                                 STRING())
                         .testResult($("f4").urlDecode(), "url_decode(f4)", "", 
STRING())
                         .testResult($("f5").urlDecode(), "url_decode(f5)", 
null, STRING()),
+                
TestSetSpec.forFunction(BuiltInFunctionDefinitions.URL_DECODE_RECURSIVE)
+                        .onFieldsWithData(
+                                "https%253A%252F%252Fflink.apache.org%252F",
+                                "https://flink.apache.org/";,
+                                null,
+                                
"inva+lid%253A%252F%252Fuser%253Apass%2540host%252Ffile%253Bparam%253Fquery%253Bp2",
+                                "",
+                                "illegal escape pattern test%")
+                        .andDataTypes(STRING(), STRING(), STRING(), STRING(), 
STRING(), STRING())
+                        .testResult(
+                                $("f0").urlDecodeRecursive(),
+                                "url_decode_recursive(f0)",
+                                "https://flink.apache.org/";,
+                                STRING())
+                        .testResult(
+                                $("f1").urlDecodeRecursive(),
+                                "url_decode_recursive(f1)",
+                                "https://flink.apache.org/";,
+                                STRING())
+                        .testResult(
+                                $("f2").urlDecodeRecursive(),
+                                "url_decode_recursive(f2)",
+                                null,
+                                STRING().nullable())
+                        .testResult(
+                                $("f3").urlDecodeRecursive(),
+                                "url_decode_recursive(f3)",
+                                "inva 
lid://user:pass@host/file;param?query;p2",
+                                STRING())
+                        .testResult(
+                                $("f4").urlDecodeRecursive(),
+                                "url_decode_recursive(f4)",
+                                "",
+                                STRING())
+                        .testResult(
+                                $("f5").urlDecodeRecursive(),
+                                "url_decode_recursive(f5)",
+                                null,
+                                STRING()),
+                
TestSetSpec.forFunction(BuiltInFunctionDefinitions.URL_DECODE_RECURSIVE)
+                        .onFieldsWithData(
+                                "https%253A%252F%252Fflink.apache.org%252F",
+                                "https%253A%252F%252Fflink.apache.org%252F",
+                                
"https%25253A%25252F%25252Fflink.apache.org%25252F")
+                        .andDataTypes(STRING(), STRING(), STRING())
+                        .testResult(
+                                $("f0").urlDecodeRecursive(2),
+                                "url_decode_recursive(f0, 2)",
+                                "https://flink.apache.org/";,
+                                STRING())
+                        .testResult(
+                                $("f1").urlDecodeRecursive(1),
+                                "url_decode_recursive(f1, 1)",
+                                "https%3A%2F%2Fflink.apache.org%2F",
+                                STRING())
+                        .testResult(
+                                $("f2").urlDecodeRecursive(3),
+                                "url_decode_recursive(f2, 3)",
+                                "https://flink.apache.org/";,
+                                STRING()),

Review Comment:
   It would be better to add the following cases:
   1. null input with `maxDepth` specified
   2. `maxDepth <= 0`
   3. `iteration > 0` and fails afterward



##########
flink-python/pyflink/table/expression.py:
##########
@@ -1439,6 +1439,26 @@ def url_decode(self) -> 'Expression[str]':
         """
         return _unary_op("urlDecode")(self)
 
+    def url_decode_recursive(self) -> 'Expression[str]':
+        """
+        Recursively decodes a URL-encoded string from 
'application/x-www-form-urlencoded'
+        format using the UTF-8 encoding scheme until no further decoding is 
possible or the
+        default max depth of 10 is reached. If the input is null, or there is 
an issue with
+        the decoding process, or the encoding scheme is not supported, will 
return null.
+        """
+        return _unary_op("urlDecodeRecursive")(self)
+
+    def url_decode_recursive_n(self, max_depth: int) -> 'Expression[str]':

Review Comment:
   We can keep only one method with a default `max_depth` value, and remove the 
`_n` at the end.



##########
flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/functions/scalar/UrlDecodeFunction.java:
##########
@@ -30,22 +31,78 @@
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 
-/** Implementation of {@link BuiltInFunctionDefinitions#URL_DECODE}. */
+/**
+ * Implementation of {@link BuiltInFunctionDefinitions#URL_DECODE} and {@link
+ * BuiltInFunctionDefinitions#URL_DECODE_RECURSIVE}.
+ */
 @Internal
 public class UrlDecodeFunction extends BuiltInScalarFunction {
 
+    private static final int DEFAULT_MAX_DECODE_DEPTH = 10;
+
+    private final boolean recursive;
+
     public UrlDecodeFunction(SpecializedFunction.SpecializedContext context) {
-        super(BuiltInFunctionDefinitions.URL_DECODE, context);
+        super(resolveDefinition(context), context);
+        this.recursive =
+                resolveDefinition(context) == 
BuiltInFunctionDefinitions.URL_DECODE_RECURSIVE;

Review Comment:
   nit: Reduce to a single call.



##########
flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/functions/scalar/UrlDecodeFunction.java:
##########
@@ -30,22 +31,78 @@
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
 
-/** Implementation of {@link BuiltInFunctionDefinitions#URL_DECODE}. */
+/**
+ * Implementation of {@link BuiltInFunctionDefinitions#URL_DECODE} and {@link
+ * BuiltInFunctionDefinitions#URL_DECODE_RECURSIVE}.
+ */
 @Internal
 public class UrlDecodeFunction extends BuiltInScalarFunction {
 
+    private static final int DEFAULT_MAX_DECODE_DEPTH = 10;
+
+    private final boolean recursive;
+
     public UrlDecodeFunction(SpecializedFunction.SpecializedContext context) {
-        super(BuiltInFunctionDefinitions.URL_DECODE, context);
+        super(resolveDefinition(context), context);
+        this.recursive =
+                resolveDefinition(context) == 
BuiltInFunctionDefinitions.URL_DECODE_RECURSIVE;
+    }
+
+    private static BuiltInFunctionDefinition resolveDefinition(
+            SpecializedFunction.SpecializedContext context) {
+        if (context.getCallContext()
+                .getFunctionDefinition()
+                .equals(BuiltInFunctionDefinitions.URL_DECODE_RECURSIVE)) {
+            return BuiltInFunctionDefinitions.URL_DECODE_RECURSIVE;
+        }
+        return BuiltInFunctionDefinitions.URL_DECODE;
     }
 
     public @Nullable StringData eval(StringData value) {
         if (value == null) {
             return null;
         }
         final Charset charset = StandardCharsets.UTF_8;
+        if (!recursive) {
+            try {
+                return 
StringData.fromString(URLDecoder.decode(value.toString(), charset.name()));
+            } catch (UnsupportedEncodingException | RuntimeException e) {
+                return null;
+            }
+        }
+
+        // Recursive with default max depth
+        return eval(value, DEFAULT_MAX_DECODE_DEPTH);
+    }
+
+    public @Nullable StringData eval(StringData value, Integer maxDepth) {

Review Comment:
   What if input is `BIGINT`?



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to