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

lzljs3620320 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new d52f438838 [core] add function name validate to avoid illegal names 
from Flink SQL (#5702)
d52f438838 is described below

commit d52f438838d5b6ede3a92493a3059d8b78034369
Author: jerry <[email protected]>
AuthorDate: Fri Jun 6 15:56:51 2025 +0800

    [core] add function name validate to avoid illegal names from Flink SQL 
(#5702)
---
 .../paimon/function/FunctionNameValidator.java     | 39 ++++++++++++++++
 .../main/java/org/apache/paimon/rest/RESTApi.java  | 20 +++++++--
 .../org/apache/paimon/rest/RESTCatalogTest.java    | 52 +++++++++++++++++++++-
 3 files changed, 105 insertions(+), 6 deletions(-)

diff --git 
a/paimon-api/src/main/java/org/apache/paimon/function/FunctionNameValidator.java
 
b/paimon-api/src/main/java/org/apache/paimon/function/FunctionNameValidator.java
new file mode 100644
index 0000000000..5e70fa1dad
--- /dev/null
+++ 
b/paimon-api/src/main/java/org/apache/paimon/function/FunctionNameValidator.java
@@ -0,0 +1,39 @@
+/*
+ * 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.paimon.function;
+
+import java.util.regex.Pattern;
+
+/** Validator for function name. */
+public class FunctionNameValidator {
+    private static final Pattern FUNCTION_NAME_PATTERN =
+            Pattern.compile("^(?=.*[A-Za-z])[A-Za-z0-9._-]+$");
+
+    public static boolean isValidName(String name) {
+        return org.apache.paimon.utils.StringUtils.isNotEmpty(name)
+                && FUNCTION_NAME_PATTERN.matcher(name).matches();
+    }
+
+    public static void check(String name) {
+        boolean isValid = isValidName(name);
+        if (!isValid) {
+            throw new IllegalArgumentException("Invalid function name: " + 
name);
+        }
+    }
+}
diff --git a/paimon-api/src/main/java/org/apache/paimon/rest/RESTApi.java 
b/paimon-api/src/main/java/org/apache/paimon/rest/RESTApi.java
index 4c2f41baa5..24ee8c670b 100644
--- a/paimon-api/src/main/java/org/apache/paimon/rest/RESTApi.java
+++ b/paimon-api/src/main/java/org/apache/paimon/rest/RESTApi.java
@@ -24,6 +24,7 @@ import org.apache.paimon.annotation.Public;
 import org.apache.paimon.annotation.VisibleForTesting;
 import org.apache.paimon.catalog.Identifier;
 import org.apache.paimon.function.FunctionChange;
+import org.apache.paimon.function.FunctionNameValidator;
 import org.apache.paimon.options.Options;
 import org.apache.paimon.partition.Partition;
 import org.apache.paimon.partition.PartitionStatistics;
@@ -51,6 +52,7 @@ import org.apache.paimon.rest.responses.AlterDatabaseResponse;
 import org.apache.paimon.rest.responses.AuthTableQueryResponse;
 import org.apache.paimon.rest.responses.CommitTableResponse;
 import org.apache.paimon.rest.responses.ConfigResponse;
+import org.apache.paimon.rest.responses.ErrorResponse;
 import org.apache.paimon.rest.responses.GetDatabaseResponse;
 import org.apache.paimon.rest.responses.GetFunctionResponse;
 import org.apache.paimon.rest.responses.GetTableResponse;
@@ -851,10 +853,17 @@ public class RESTApi {
      * @throws ForbiddenException if the user lacks permission to access the 
function
      */
     public GetFunctionResponse getFunction(Identifier identifier) {
-        return client.get(
-                resourcePaths.function(identifier.getDatabaseName(), 
identifier.getObjectName()),
-                GetFunctionResponse.class,
-                restAuthFunction);
+        if (FunctionNameValidator.isValidName(identifier.getObjectName())) {
+            return client.get(
+                    resourcePaths.function(
+                            identifier.getDatabaseName(), 
identifier.getObjectName()),
+                    GetFunctionResponse.class,
+                    restAuthFunction);
+        }
+        throw new NoSuchResourceException(
+                ErrorResponse.RESOURCE_TYPE_FUNCTION,
+                identifier.getObjectName(),
+                "Invalid function name: " + identifier.getObjectName());
     }
 
     /**
@@ -868,6 +877,7 @@ public class RESTApi {
      */
     public void createFunction(
             Identifier identifier, org.apache.paimon.function.Function 
function) {
+        FunctionNameValidator.check(identifier.getObjectName());
         client.post(
                 resourcePaths.functions(identifier.getDatabaseName()),
                 new CreateFunctionRequest(function),
@@ -883,6 +893,7 @@ public class RESTApi {
      *     this function
      */
     public void dropFunction(Identifier identifier) {
+        FunctionNameValidator.check(identifier.getObjectName());
         client.delete(
                 resourcePaths.function(identifier.getDatabaseName(), 
identifier.getObjectName()),
                 restAuthFunction);
@@ -897,6 +908,7 @@ public class RESTApi {
      * @throws ForbiddenException if the user lacks permission to modify the 
function
      */
     public void alterFunction(Identifier identifier, List<FunctionChange> 
changes) {
+        FunctionNameValidator.check(identifier.getObjectName());
         client.post(
                 resourcePaths.function(identifier.getDatabaseName(), 
identifier.getObjectName()),
                 new AlterFunctionRequest(changes),
diff --git 
a/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java 
b/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java
index ec2617a934..a259a84e10 100644
--- a/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java
+++ b/paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java
@@ -31,6 +31,7 @@ import org.apache.paimon.data.InternalRow;
 import org.apache.paimon.function.Function;
 import org.apache.paimon.function.FunctionChange;
 import org.apache.paimon.function.FunctionDefinition;
+import org.apache.paimon.function.FunctionNameValidator;
 import org.apache.paimon.options.Options;
 import org.apache.paimon.partition.Partition;
 import org.apache.paimon.partition.PartitionStatistics;
@@ -1573,8 +1574,38 @@ public abstract class RESTCatalogTest extends 
CatalogTestBase {
 
     @Test
     void testFunction() throws Exception {
-        Identifier identifier = new Identifier("rest_catalog_db", "function");
-        catalog.createDatabase(identifier.getDatabaseName(), false);
+        Identifier identifierWithSlash = new Identifier("rest_catalog_db", 
"function/");
+        catalog.createDatabase(identifierWithSlash.getDatabaseName(), false);
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->
+                        catalog.createFunction(
+                                identifierWithSlash,
+                                MockRESTMessage.function(identifierWithSlash),
+                                false));
+        assertThrows(
+                Catalog.FunctionNotExistException.class,
+                () -> catalog.getFunction(identifierWithSlash));
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> catalog.dropFunction(identifierWithSlash, true));
+
+        Identifier identifierWithoutAlphabet = new 
Identifier("rest_catalog_db", "-");
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->
+                        catalog.createFunction(
+                                identifierWithoutAlphabet,
+                                
MockRESTMessage.function(identifierWithoutAlphabet),
+                                false));
+        assertThrows(
+                Catalog.FunctionNotExistException.class,
+                () -> catalog.getFunction(identifierWithoutAlphabet));
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> catalog.dropFunction(identifierWithoutAlphabet, true));
+
+        Identifier identifier = new Identifier("rest_catalog_db", 
"function.na_me-01");
         Function function = MockRESTMessage.function(identifier);
 
         catalog.createFunction(identifier, function, true);
@@ -1674,6 +1705,23 @@ public abstract class RESTCatalogTest extends 
CatalogTestBase {
                 () -> catalog.alterFunction(identifier, 
ImmutableList.of(dropDefinition), false));
     }
 
+    @Test
+    public void testValidateFunctionName() throws Exception {
+        assertDoesNotThrow(() -> FunctionNameValidator.check("a"));
+        assertDoesNotThrow(() -> FunctionNameValidator.check("a1_"));
+        assertDoesNotThrow(() -> FunctionNameValidator.check("a-b_c"));
+        assertDoesNotThrow(() -> FunctionNameValidator.check("a-b_c.1"));
+
+        assertThrows(IllegalArgumentException.class, () -> 
FunctionNameValidator.check("a\\/b"));
+        assertThrows(IllegalArgumentException.class, () -> 
FunctionNameValidator.check("a$?b"));
+        assertThrows(IllegalArgumentException.class, () -> 
FunctionNameValidator.check("a@b"));
+        assertThrows(IllegalArgumentException.class, () -> 
FunctionNameValidator.check("a*b"));
+        assertThrows(IllegalArgumentException.class, () -> 
FunctionNameValidator.check("123"));
+        assertThrows(IllegalArgumentException.class, () -> 
FunctionNameValidator.check("_-"));
+        assertThrows(IllegalArgumentException.class, () -> 
FunctionNameValidator.check(""));
+        assertThrows(IllegalArgumentException.class, () -> 
FunctionNameValidator.check(null));
+    }
+
     @Test
     void testTableAuth() throws Exception {
         Identifier identifier = Identifier.create("test_table_db", 
"auth_table");

Reply via email to