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");