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

jshao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git


The following commit(s) were added to refs/heads/main by this push:
     new 904fd7f9bd [#9529] feat(server): Add server-side REST interface for 
UDFs (part-1) (#9566)
904fd7f9bd is described below

commit 904fd7f9bd7d3ef3137ba62451c8465bf013c315
Author: mchades <[email protected]>
AuthorDate: Sat Jan 24 03:17:44 2026 +0800

    [#9529] feat(server): Add server-side REST interface for UDFs (part-1) 
(#9566)
    
    ### What changes were proposed in this pull request?
    
    This PR implements the server-side REST API for User-Defined Functions
    (UDFs), including:
    
    - **DTO classes**: `FunctionDTO`, `FunctionDefinitionDTO`,
    `FunctionParamDTO`, `FunctionColumnDTO`, `FunctionImplDTO` (with
    `JavaImplDTO`, `PythonImplDTO`, `SQLImplDTO` subclasses),
    `FunctionResourcesDTO`
    - **Request/Response classes**: `FunctionRegisterRequest`,
    `FunctionUpdateRequest`, `FunctionUpdatesRequest`, `FunctionResponse`
    
    ### Why are the changes needed?
    
    To provide server-side REST API support for managing UDFs in Gravitino.
    
    Fix: #9529
    
    ### Does this PR introduce _any_ user-facing change?
    
    No
    
    ### How was this patch tested?
    
    - Added comprehensive unit tests for all DTO classes (`TestFunctionDTO`)
---
 .../gravitino/dto/function/FunctionColumnDTO.java  |  87 +++++++
 .../apache/gravitino/dto/function/FunctionDTO.java | 145 ++++++++++++
 .../dto/function/FunctionDefinitionDTO.java        | 120 ++++++++++
 .../gravitino/dto/function/FunctionImplDTO.java    | 115 +++++++++
 .../gravitino/dto/function/FunctionParamDTO.java   | 132 +++++++++++
 .../dto/function/FunctionResourcesDTO.java         |  81 +++++++
 .../apache/gravitino/dto/function/JavaImplDTO.java |  68 ++++++
 .../gravitino/dto/function/PythonImplDTO.java      |  75 ++++++
 .../apache/gravitino/dto/function/SQLImplDTO.java  |  67 ++++++
 .../dto/requests/FunctionRegisterRequest.java      | 100 ++++++++
 .../dto/requests/FunctionUpdateRequest.java        | 255 ++++++++++++++++++++
 .../dto/requests/FunctionUpdatesRequest.java       |  49 ++++
 .../dto/responses/FunctionListResponse.java        |  57 +++++
 .../gravitino/dto/responses/FunctionResponse.java  |  70 ++++++
 .../apache/gravitino/dto/util/DTOConverters.java   |  39 ++++
 .../gravitino/dto/function/TestFunctionDTO.java    | 260 +++++++++++++++++++++
 .../dto/requests/TestFunctionRegisterRequest.java  | 179 ++++++++++++++
 .../dto/requests/TestFunctionUpdateRequest.java    | 253 ++++++++++++++++++++
 .../dto/requests/TestFunctionUpdatesRequest.java   |  82 +++++++
 19 files changed, 2234 insertions(+)

diff --git 
a/common/src/main/java/org/apache/gravitino/dto/function/FunctionColumnDTO.java 
b/common/src/main/java/org/apache/gravitino/dto/function/FunctionColumnDTO.java
new file mode 100644
index 0000000000..df96a2d31e
--- /dev/null
+++ 
b/common/src/main/java/org/apache/gravitino/dto/function/FunctionColumnDTO.java
@@ -0,0 +1,87 @@
+/*
+ * 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.gravitino.dto.function;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import javax.annotation.Nullable;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.apache.gravitino.function.FunctionColumn;
+import org.apache.gravitino.json.JsonUtils;
+import org.apache.gravitino.rel.types.Type;
+
+/** DTO for function column. */
+@Getter
+@EqualsAndHashCode
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor
+@Builder(setterPrefix = "with")
+public class FunctionColumnDTO {
+
+  @JsonProperty("name")
+  private String name;
+
+  @JsonProperty("dataType")
+  @JsonSerialize(using = JsonUtils.TypeSerializer.class)
+  @JsonDeserialize(using = JsonUtils.TypeDeserializer.class)
+  private Type dataType;
+
+  @Nullable
+  @JsonProperty("comment")
+  private String comment;
+
+  /**
+   * Convert this DTO to a {@link FunctionColumn} instance.
+   *
+   * @return The function column.
+   */
+  public FunctionColumn toFunctionColumn() {
+    return FunctionColumn.of(name, dataType, comment);
+  }
+
+  /**
+   * Create a {@link FunctionColumnDTO} from a {@link FunctionColumn} instance.
+   *
+   * @param column The function column.
+   * @return The function column DTO.
+   */
+  public static FunctionColumnDTO fromFunctionColumn(FunctionColumn column) {
+    return new FunctionColumnDTO(column.name(), column.dataType(), 
column.comment());
+  }
+
+  @Override
+  public String toString() {
+    return "FunctionColumnDTO{"
+        + "name='"
+        + name
+        + '\''
+        + ", dataType="
+        + dataType
+        + ", comment='"
+        + comment
+        + '\''
+        + '}';
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/function/FunctionDTO.java 
b/common/src/main/java/org/apache/gravitino/dto/function/FunctionDTO.java
new file mode 100644
index 0000000000..4fa5e391b7
--- /dev/null
+++ b/common/src/main/java/org/apache/gravitino/dto/function/FunctionDTO.java
@@ -0,0 +1,145 @@
+/*
+ * 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.gravitino.dto.function;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import java.util.Arrays;
+import javax.annotation.Nullable;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
+import org.apache.gravitino.Audit;
+import org.apache.gravitino.dto.AuditDTO;
+import org.apache.gravitino.function.Function;
+import org.apache.gravitino.function.FunctionColumn;
+import org.apache.gravitino.function.FunctionDefinition;
+import org.apache.gravitino.function.FunctionType;
+import org.apache.gravitino.json.JsonUtils;
+import org.apache.gravitino.rel.types.Type;
+
+/** Represents a Function DTO (Data Transfer Object). */
+@NoArgsConstructor
+@AllArgsConstructor
+@Builder(setterPrefix = "with")
+@EqualsAndHashCode
+public class FunctionDTO implements Function {
+
+  @JsonProperty("name")
+  private String name;
+
+  @JsonProperty("functionType")
+  private FunctionType functionType;
+
+  @JsonProperty("deterministic")
+  private boolean deterministic;
+
+  @Nullable
+  @JsonProperty("comment")
+  private String comment;
+
+  @Nullable
+  @JsonProperty("returnType")
+  @JsonSerialize(using = JsonUtils.TypeSerializer.class)
+  @JsonDeserialize(using = JsonUtils.TypeDeserializer.class)
+  private Type returnType;
+
+  @JsonProperty("returnColumns")
+  private FunctionColumnDTO[] returnColumns;
+
+  @JsonProperty("definitions")
+  private FunctionDefinitionDTO[] definitions;
+
+  @JsonProperty("audit")
+  private AuditDTO audit;
+
+  @Override
+  public String name() {
+    return name;
+  }
+
+  @Override
+  public FunctionType functionType() {
+    return functionType;
+  }
+
+  @Override
+  public boolean deterministic() {
+    return deterministic;
+  }
+
+  @Override
+  public String comment() {
+    return comment;
+  }
+
+  @Override
+  public Type returnType() {
+    return returnType;
+  }
+
+  @Override
+  public FunctionColumn[] returnColumns() {
+    if (returnColumns == null) {
+      return new FunctionColumn[0];
+    }
+    return Arrays.stream(returnColumns)
+        .map(FunctionColumnDTO::toFunctionColumn)
+        .toArray(FunctionColumn[]::new);
+  }
+
+  @Override
+  public FunctionDefinition[] definitions() {
+    if (definitions == null) {
+      return new FunctionDefinition[0];
+    }
+    return definitions;
+  }
+
+  @Override
+  public Audit auditInfo() {
+    return audit;
+  }
+
+  @Override
+  public String toString() {
+    return "FunctionDTO{"
+        + "name='"
+        + name
+        + '\''
+        + ", functionType="
+        + functionType
+        + ", deterministic="
+        + deterministic
+        + ", comment='"
+        + comment
+        + '\''
+        + ", returnType="
+        + returnType
+        + ", returnColumns="
+        + Arrays.toString(returnColumns)
+        + ", definitions="
+        + Arrays.toString(definitions)
+        + ", audit="
+        + audit
+        + '}';
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/function/FunctionDefinitionDTO.java
 
b/common/src/main/java/org/apache/gravitino/dto/function/FunctionDefinitionDTO.java
new file mode 100644
index 0000000000..9182ae86db
--- /dev/null
+++ 
b/common/src/main/java/org/apache/gravitino/dto/function/FunctionDefinitionDTO.java
@@ -0,0 +1,120 @@
+/*
+ * 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.gravitino.dto.function;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Arrays;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.apache.gravitino.function.FunctionDefinition;
+import org.apache.gravitino.function.FunctionDefinitions;
+import org.apache.gravitino.function.FunctionImpl;
+import org.apache.gravitino.function.FunctionParam;
+
+/** DTO for function definition. */
+@Getter
+@EqualsAndHashCode
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+@Builder(setterPrefix = "with")
+public class FunctionDefinitionDTO implements FunctionDefinition {
+
+  @JsonProperty("parameters")
+  private FunctionParamDTO[] parameters;
+
+  @JsonProperty("impls")
+  private FunctionImplDTO[] impls;
+
+  @Override
+  public FunctionParam[] parameters() {
+    if (parameters == null) {
+      return new FunctionParam[0];
+    }
+    return parameters;
+  }
+
+  @Override
+  public FunctionImpl[] impls() {
+    if (impls == null) {
+      return new FunctionImpl[0];
+    }
+    return 
Arrays.stream(impls).map(FunctionImplDTO::toFunctionImpl).toArray(FunctionImpl[]::new);
+  }
+
+  /**
+   * Convert this DTO to a {@link FunctionDefinition} instance.
+   *
+   * @return The function definition.
+   */
+  public FunctionDefinition toFunctionDefinition() {
+    FunctionParam[] params =
+        parameters == null
+            ? new FunctionParam[0]
+            : Arrays.stream(parameters)
+                .map(FunctionParamDTO::toFunctionParam)
+                .toArray(FunctionParam[]::new);
+    FunctionImpl[] implArr =
+        impls == null
+            ? new FunctionImpl[0]
+            : Arrays.stream(impls)
+                .map(FunctionImplDTO::toFunctionImpl)
+                .toArray(FunctionImpl[]::new);
+    return FunctionDefinitions.of(params, implArr);
+  }
+
+  /**
+   * Create a {@link FunctionDefinitionDTO} from a {@link FunctionDefinition} 
instance.
+   *
+   * @param definition The function definition.
+   * @return The function definition DTO.
+   */
+  public static FunctionDefinitionDTO 
fromFunctionDefinition(FunctionDefinition definition) {
+    FunctionParamDTO[] paramDTOs =
+        definition.parameters() == null
+            ? new FunctionParamDTO[0]
+            : Arrays.stream(definition.parameters())
+                .map(
+                    param ->
+                        param instanceof FunctionParamDTO
+                            ? (FunctionParamDTO) param
+                            : FunctionParamDTO.fromFunctionParam(param))
+                .toArray(FunctionParamDTO[]::new);
+    FunctionImplDTO[] implDTOs =
+        definition.impls() == null
+            ? new FunctionImplDTO[0]
+            : Arrays.stream(definition.impls())
+                .map(FunctionImplDTO::fromFunctionImpl)
+                .toArray(FunctionImplDTO[]::new);
+    return new FunctionDefinitionDTO(paramDTOs, implDTOs);
+  }
+
+  @Override
+  public String toString() {
+    return "FunctionDefinitionDTO{"
+        + "parameters="
+        + Arrays.toString(parameters)
+        + ", impls="
+        + Arrays.toString(impls)
+        + '}';
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/function/FunctionImplDTO.java 
b/common/src/main/java/org/apache/gravitino/dto/function/FunctionImplDTO.java
new file mode 100644
index 0000000000..6aef7a1417
--- /dev/null
+++ 
b/common/src/main/java/org/apache/gravitino/dto/function/FunctionImplDTO.java
@@ -0,0 +1,115 @@
+/*
+ * 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.gravitino.dto.function;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import java.util.Map;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import org.apache.gravitino.function.FunctionImpl;
+import org.apache.gravitino.function.JavaImpl;
+import org.apache.gravitino.function.PythonImpl;
+import org.apache.gravitino.function.SQLImpl;
+
+/** DTO for function implementation. */
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, 
property = "language")
+@JsonSubTypes({
+  @JsonSubTypes.Type(value = SQLImplDTO.class, name = "SQL"),
+  @JsonSubTypes.Type(value = JavaImplDTO.class, name = "JAVA"),
+  @JsonSubTypes.Type(value = PythonImplDTO.class, name = "PYTHON")
+})
+@Getter
+@EqualsAndHashCode
+public abstract class FunctionImplDTO {
+
+  @JsonProperty("runtime")
+  private String runtime;
+
+  @JsonProperty("resources")
+  private FunctionResourcesDTO resources;
+
+  @JsonProperty("properties")
+  private Map<String, String> properties;
+
+  /** Default constructor for Jackson. */
+  protected FunctionImplDTO() {}
+
+  /**
+   * Constructor for FunctionImplDTO.
+   *
+   * @param runtime The runtime type.
+   * @param resources The function resources.
+   * @param properties The properties.
+   */
+  protected FunctionImplDTO(
+      String runtime, FunctionResourcesDTO resources, Map<String, String> 
properties) {
+    this.runtime = runtime;
+    this.resources = resources;
+    this.properties = properties;
+  }
+
+  /**
+   * Get the language of this implementation.
+   *
+   * @return The language.
+   */
+  public abstract FunctionImpl.Language language();
+
+  /**
+   * Convert this DTO to a {@link FunctionImpl} instance.
+   *
+   * @return The function implementation.
+   */
+  public abstract FunctionImpl toFunctionImpl();
+
+  /**
+   * Create a {@link FunctionImplDTO} from a {@link FunctionImpl} instance.
+   *
+   * @param impl The function implementation.
+   * @return The function implementation DTO.
+   */
+  public static FunctionImplDTO fromFunctionImpl(FunctionImpl impl) {
+    if (impl instanceof SQLImpl) {
+      SQLImpl sqlImpl = (SQLImpl) impl;
+      return new SQLImplDTO(
+          sqlImpl.runtime().name(),
+          FunctionResourcesDTO.fromFunctionResources(sqlImpl.resources()),
+          sqlImpl.properties(),
+          sqlImpl.sql());
+    } else if (impl instanceof JavaImpl) {
+      JavaImpl javaImpl = (JavaImpl) impl;
+      return new JavaImplDTO(
+          javaImpl.runtime().name(),
+          FunctionResourcesDTO.fromFunctionResources(javaImpl.resources()),
+          javaImpl.properties(),
+          javaImpl.className());
+    } else if (impl instanceof PythonImpl) {
+      PythonImpl pythonImpl = (PythonImpl) impl;
+      return new PythonImplDTO(
+          pythonImpl.runtime().name(),
+          FunctionResourcesDTO.fromFunctionResources(pythonImpl.resources()),
+          pythonImpl.properties(),
+          pythonImpl.handler(),
+          pythonImpl.codeBlock());
+    }
+    throw new IllegalArgumentException("Unsupported implementation type: " + 
impl.getClass());
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/function/FunctionParamDTO.java 
b/common/src/main/java/org/apache/gravitino/dto/function/FunctionParamDTO.java
new file mode 100644
index 0000000000..895350be93
--- /dev/null
+++ 
b/common/src/main/java/org/apache/gravitino/dto/function/FunctionParamDTO.java
@@ -0,0 +1,132 @@
+/*
+ * 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.gravitino.dto.function;
+
+import static org.apache.gravitino.dto.util.DTOConverters.toFunctionArg;
+import static org.apache.gravitino.rel.Column.DEFAULT_VALUE_NOT_SET;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import javax.annotation.Nullable;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.apache.gravitino.function.FunctionParam;
+import org.apache.gravitino.function.FunctionParams;
+import org.apache.gravitino.json.JsonUtils;
+import org.apache.gravitino.rel.Column;
+import org.apache.gravitino.rel.expressions.Expression;
+import org.apache.gravitino.rel.types.Type;
+
+/** DTO for function parameter. */
+@Getter
+@EqualsAndHashCode
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+@Builder(setterPrefix = "with")
+public class FunctionParamDTO implements FunctionParam {
+
+  @JsonProperty("name")
+  private String name;
+
+  @JsonProperty("dataType")
+  @JsonSerialize(using = JsonUtils.TypeSerializer.class)
+  @JsonDeserialize(using = JsonUtils.TypeDeserializer.class)
+  private Type dataType;
+
+  @Nullable
+  @JsonProperty("comment")
+  private String comment;
+
+  @JsonProperty("defaultValue")
+  @JsonInclude(JsonInclude.Include.NON_EMPTY)
+  @JsonSerialize(using = JsonUtils.ColumnDefaultValueSerializer.class)
+  @JsonDeserialize(using = JsonUtils.ColumnDefaultValueDeserializer.class)
+  @Builder.Default
+  private Expression defaultValue = DEFAULT_VALUE_NOT_SET;
+
+  @Override
+  public String name() {
+    return name;
+  }
+
+  @Override
+  public Type dataType() {
+    return dataType;
+  }
+
+  @Override
+  public String comment() {
+    return comment;
+  }
+
+  @Override
+  public Expression defaultValue() {
+    return defaultValue;
+  }
+
+  /**
+   * Convert this DTO to a {@link FunctionParam} instance.
+   *
+   * @return The function parameter.
+   */
+  public FunctionParam toFunctionParam() {
+    return FunctionParams.of(name, dataType, comment, defaultValue());
+  }
+
+  /**
+   * Create a {@link FunctionParamDTO} from a {@link FunctionParam} instance.
+   *
+   * @param param The function parameter.
+   * @return The function parameter DTO.
+   */
+  public static FunctionParamDTO fromFunctionParam(FunctionParam param) {
+    return FunctionParamDTO.builder()
+        .withName(param.name())
+        .withDataType(param.dataType())
+        .withComment(param.comment())
+        .withDefaultValue(
+            (param.defaultValue() == null
+                    || 
param.defaultValue().equals(Column.DEFAULT_VALUE_NOT_SET))
+                ? Column.DEFAULT_VALUE_NOT_SET
+                : toFunctionArg(param.defaultValue()))
+        .build();
+  }
+
+  @Override
+  public String toString() {
+    return "FunctionParamDTO{"
+        + "name='"
+        + name
+        + '\''
+        + ", dataType="
+        + dataType
+        + ", comment='"
+        + comment
+        + '\''
+        + ", defaultValue="
+        + defaultValue
+        + '}';
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/function/FunctionResourcesDTO.java
 
b/common/src/main/java/org/apache/gravitino/dto/function/FunctionResourcesDTO.java
new file mode 100644
index 0000000000..8454ad77cd
--- /dev/null
+++ 
b/common/src/main/java/org/apache/gravitino/dto/function/FunctionResourcesDTO.java
@@ -0,0 +1,81 @@
+/*
+ * 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.gravitino.dto.function;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Arrays;
+import lombok.AccessLevel;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.apache.gravitino.function.FunctionResources;
+
+/** DTO for function resources. */
+@Getter
+@EqualsAndHashCode
+@NoArgsConstructor(access = AccessLevel.PRIVATE)
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
+@Builder(setterPrefix = "with")
+public class FunctionResourcesDTO {
+
+  @JsonProperty("jars")
+  private String[] jars;
+
+  @JsonProperty("files")
+  private String[] files;
+
+  @JsonProperty("archives")
+  private String[] archives;
+
+  /**
+   * Convert this DTO to a {@link FunctionResources} instance.
+   *
+   * @return The function resources.
+   */
+  public FunctionResources toFunctionResources() {
+    return FunctionResources.of(jars, files, archives);
+  }
+
+  /**
+   * Create a {@link FunctionResourcesDTO} from a {@link FunctionResources} 
instance.
+   *
+   * @param resources The function resources.
+   * @return The function resources DTO.
+   */
+  public static FunctionResourcesDTO fromFunctionResources(FunctionResources 
resources) {
+    if (resources == null) {
+      return null;
+    }
+    return new FunctionResourcesDTO(resources.jars(), resources.files(), 
resources.archives());
+  }
+
+  @Override
+  public String toString() {
+    return "FunctionResourcesDTO{"
+        + "jars="
+        + Arrays.toString(jars)
+        + ", files="
+        + Arrays.toString(files)
+        + ", archives="
+        + Arrays.toString(archives)
+        + '}';
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/function/JavaImplDTO.java 
b/common/src/main/java/org/apache/gravitino/dto/function/JavaImplDTO.java
new file mode 100644
index 0000000000..33e0d02ab5
--- /dev/null
+++ b/common/src/main/java/org/apache/gravitino/dto/function/JavaImplDTO.java
@@ -0,0 +1,68 @@
+/*
+ * 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.gravitino.dto.function;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Map;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import org.apache.gravitino.function.FunctionImpl;
+import org.apache.gravitino.function.FunctionImpls;
+
+/** Java implementation DTO. */
+@Getter
+@EqualsAndHashCode(callSuper = true)
+public class JavaImplDTO extends FunctionImplDTO {
+
+  @JsonProperty("className")
+  private String className;
+
+  private JavaImplDTO() {}
+
+  /**
+   * Constructor for JavaImplDTO.
+   *
+   * @param runtime The runtime type.
+   * @param resources The function resources.
+   * @param properties The properties.
+   * @param className The fully qualified class name.
+   */
+  public JavaImplDTO(
+      String runtime,
+      FunctionResourcesDTO resources,
+      Map<String, String> properties,
+      String className) {
+    super(runtime, resources, properties);
+    this.className = className;
+  }
+
+  @Override
+  public FunctionImpl.Language language() {
+    return FunctionImpl.Language.JAVA;
+  }
+
+  @Override
+  public FunctionImpl toFunctionImpl() {
+    return FunctionImpls.ofJava(
+        FunctionImpl.RuntimeType.fromString(getRuntime()),
+        className,
+        getResources() != null ? getResources().toFunctionResources() : null,
+        getProperties());
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/function/PythonImplDTO.java 
b/common/src/main/java/org/apache/gravitino/dto/function/PythonImplDTO.java
new file mode 100644
index 0000000000..e9b38616be
--- /dev/null
+++ b/common/src/main/java/org/apache/gravitino/dto/function/PythonImplDTO.java
@@ -0,0 +1,75 @@
+/*
+ * 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.gravitino.dto.function;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Map;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import org.apache.gravitino.function.FunctionImpl;
+import org.apache.gravitino.function.FunctionImpls;
+
+/** Python implementation DTO. */
+@Getter
+@EqualsAndHashCode(callSuper = true)
+public class PythonImplDTO extends FunctionImplDTO {
+
+  @JsonProperty("handler")
+  private String handler;
+
+  @JsonProperty("codeBlock")
+  private String codeBlock;
+
+  private PythonImplDTO() {}
+
+  /**
+   * Constructor for PythonImplDTO.
+   *
+   * @param runtime The runtime type.
+   * @param resources The function resources.
+   * @param properties The properties.
+   * @param handler The Python handler function.
+   * @param codeBlock The Python code block.
+   */
+  public PythonImplDTO(
+      String runtime,
+      FunctionResourcesDTO resources,
+      Map<String, String> properties,
+      String handler,
+      String codeBlock) {
+    super(runtime, resources, properties);
+    this.handler = handler;
+    this.codeBlock = codeBlock;
+  }
+
+  @Override
+  public FunctionImpl.Language language() {
+    return FunctionImpl.Language.PYTHON;
+  }
+
+  @Override
+  public FunctionImpl toFunctionImpl() {
+    return FunctionImpls.ofPython(
+        FunctionImpl.RuntimeType.fromString(getRuntime()),
+        handler,
+        codeBlock,
+        getResources() != null ? getResources().toFunctionResources() : null,
+        getProperties());
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/function/SQLImplDTO.java 
b/common/src/main/java/org/apache/gravitino/dto/function/SQLImplDTO.java
new file mode 100644
index 0000000000..87f69a83f9
--- /dev/null
+++ b/common/src/main/java/org/apache/gravitino/dto/function/SQLImplDTO.java
@@ -0,0 +1,67 @@
+/*
+ * 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.gravitino.dto.function;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.Map;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import org.apache.gravitino.function.FunctionImpl;
+import org.apache.gravitino.function.FunctionImpls;
+
+/** SQL implementation DTO. */
+@Getter
+@EqualsAndHashCode(callSuper = true)
+public class SQLImplDTO extends FunctionImplDTO {
+
+  @JsonProperty("sql")
+  private String sql;
+
+  private SQLImplDTO() {
+    super();
+  }
+
+  /**
+   * Constructor for SQLImplDTO.
+   *
+   * @param runtime The runtime type.
+   * @param resources The function resources.
+   * @param properties The properties.
+   * @param sql The SQL expression.
+   */
+  public SQLImplDTO(
+      String runtime, FunctionResourcesDTO resources, Map<String, String> 
properties, String sql) {
+    super(runtime, resources, properties);
+    this.sql = sql;
+  }
+
+  @Override
+  public FunctionImpl.Language language() {
+    return FunctionImpl.Language.SQL;
+  }
+
+  @Override
+  public FunctionImpl toFunctionImpl() {
+    return FunctionImpls.ofSql(
+        FunctionImpl.RuntimeType.fromString(getRuntime()),
+        sql,
+        getResources() != null ? getResources().toFunctionResources() : null,
+        getProperties());
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/requests/FunctionRegisterRequest.java
 
b/common/src/main/java/org/apache/gravitino/dto/requests/FunctionRegisterRequest.java
new file mode 100644
index 0000000000..dc8c2c3841
--- /dev/null
+++ 
b/common/src/main/java/org/apache/gravitino/dto/requests/FunctionRegisterRequest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.gravitino.dto.requests;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.google.common.base.Preconditions;
+import javax.annotation.Nullable;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.dto.function.FunctionColumnDTO;
+import org.apache.gravitino.dto.function.FunctionDefinitionDTO;
+import org.apache.gravitino.function.FunctionType;
+import org.apache.gravitino.json.JsonUtils;
+import org.apache.gravitino.rel.types.Type;
+import org.apache.gravitino.rest.RESTRequest;
+
+/** Represents a request to register a function. */
+@Getter
+@EqualsAndHashCode
+@ToString
+@Builder(setterPrefix = "with")
+@NoArgsConstructor
+@AllArgsConstructor
+public class FunctionRegisterRequest implements RESTRequest {
+
+  @JsonProperty("name")
+  private String name;
+
+  @JsonProperty("functionType")
+  private FunctionType functionType;
+
+  @JsonProperty("deterministic")
+  private boolean deterministic;
+
+  @Nullable
+  @JsonProperty("comment")
+  private String comment;
+
+  @Nullable
+  @JsonProperty("returnType")
+  @JsonSerialize(using = JsonUtils.TypeSerializer.class)
+  @JsonDeserialize(using = JsonUtils.TypeDeserializer.class)
+  private Type returnType;
+
+  @Nullable
+  @JsonProperty("returnColumns")
+  private FunctionColumnDTO[] returnColumns;
+
+  @JsonProperty("definitions")
+  private FunctionDefinitionDTO[] definitions;
+
+  /**
+   * Validates the request.
+   *
+   * @throws IllegalArgumentException if the request is invalid.
+   */
+  @Override
+  public void validate() throws IllegalArgumentException {
+    Preconditions.checkArgument(
+        StringUtils.isNotBlank(name), "\"name\" field is required and cannot 
be empty");
+    Preconditions.checkArgument(functionType != null, "\"functionType\" field 
is required");
+    Preconditions.checkArgument(
+        definitions != null && definitions.length > 0,
+        "\"definitions\" field is required and cannot be empty");
+
+    if (functionType == FunctionType.TABLE) {
+      Preconditions.checkArgument(
+          returnColumns != null && returnColumns.length > 0,
+          "\"returnColumns\" is required for TABLE function type");
+    } else if (functionType == FunctionType.SCALAR || functionType == 
FunctionType.AGGREGATE) {
+      Preconditions.checkArgument(
+          returnType != null, "\"returnType\" is required for SCALAR or 
AGGREGATE function type");
+    } else {
+      throw new IllegalArgumentException("Unsupported function type: " + 
functionType);
+    }
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/requests/FunctionUpdateRequest.java
 
b/common/src/main/java/org/apache/gravitino/dto/requests/FunctionUpdateRequest.java
new file mode 100644
index 0000000000..f0cbdef3a4
--- /dev/null
+++ 
b/common/src/main/java/org/apache/gravitino/dto/requests/FunctionUpdateRequest.java
@@ -0,0 +1,255 @@
+/*
+ * 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.gravitino.dto.requests;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSubTypes;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.google.common.base.Preconditions;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+import org.apache.gravitino.dto.function.FunctionDefinitionDTO;
+import org.apache.gravitino.dto.function.FunctionImplDTO;
+import org.apache.gravitino.dto.function.FunctionParamDTO;
+import org.apache.gravitino.function.FunctionChange;
+import org.apache.gravitino.function.FunctionDefinition;
+import org.apache.gravitino.function.FunctionImpl;
+import org.apache.gravitino.function.FunctionParam;
+import org.apache.gravitino.rest.RESTRequest;
+
+/** Request to update a function. */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
+@JsonSubTypes({
+  @JsonSubTypes.Type(
+      value = FunctionUpdateRequest.UpdateCommentRequest.class,
+      name = "updateComment"),
+  @JsonSubTypes.Type(
+      value = FunctionUpdateRequest.AddDefinitionRequest.class,
+      name = "addDefinition"),
+  @JsonSubTypes.Type(
+      value = FunctionUpdateRequest.RemoveDefinitionRequest.class,
+      name = "removeDefinition"),
+  @JsonSubTypes.Type(value = FunctionUpdateRequest.AddImplRequest.class, name 
= "addImpl"),
+  @JsonSubTypes.Type(value = FunctionUpdateRequest.UpdateImplRequest.class, 
name = "updateImpl"),
+  @JsonSubTypes.Type(value = FunctionUpdateRequest.RemoveImplRequest.class, 
name = "removeImpl")
+})
+public interface FunctionUpdateRequest extends RESTRequest {
+
+  /**
+   * Returns the function change.
+   *
+   * @return the function change.
+   */
+  FunctionChange functionChange();
+
+  /** The function update request for updating the comment of a function. */
+  @EqualsAndHashCode
+  @NoArgsConstructor(force = true)
+  @AllArgsConstructor
+  @ToString
+  class UpdateCommentRequest implements FunctionUpdateRequest {
+
+    @Getter
+    @JsonProperty("newComment")
+    private final String newComment;
+
+    @Override
+    public FunctionChange functionChange() {
+      return FunctionChange.updateComment(newComment);
+    }
+
+    @Override
+    public void validate() throws IllegalArgumentException {
+      // newComment can be null or empty to clear the comment
+    }
+  }
+
+  /** The function update request for adding a definition to a function. */
+  @EqualsAndHashCode
+  @NoArgsConstructor(force = true)
+  @AllArgsConstructor
+  @ToString
+  class AddDefinitionRequest implements FunctionUpdateRequest {
+
+    @Getter
+    @JsonProperty("definition")
+    private final FunctionDefinitionDTO definition;
+
+    @Override
+    public FunctionChange functionChange() {
+      FunctionDefinition def = definition.toFunctionDefinition();
+      return FunctionChange.addDefinition(def);
+    }
+
+    @Override
+    public void validate() throws IllegalArgumentException {
+      Preconditions.checkArgument(
+          definition != null, "\"definition\" field is required and cannot be 
null");
+    }
+  }
+
+  /** The function update request for removing a definition from a function. */
+  @EqualsAndHashCode
+  @NoArgsConstructor(force = true)
+  @AllArgsConstructor
+  @ToString
+  class RemoveDefinitionRequest implements FunctionUpdateRequest {
+
+    @Getter
+    @JsonProperty("parameters")
+    private final FunctionParamDTO[] parameters;
+
+    @Override
+    public FunctionChange functionChange() {
+      FunctionParam[] params = new FunctionParam[parameters == null ? 0 : 
parameters.length];
+      if (parameters != null) {
+        for (int i = 0; i < parameters.length; i++) {
+          params[i] = parameters[i].toFunctionParam();
+        }
+      }
+      return FunctionChange.removeDefinition(params);
+    }
+
+    @Override
+    public void validate() throws IllegalArgumentException {
+      Preconditions.checkArgument(
+          parameters != null, "\"parameters\" field is required and cannot be 
null");
+    }
+  }
+
+  /** The function update request for adding an implementation to a 
definition. */
+  @EqualsAndHashCode
+  @NoArgsConstructor(force = true)
+  @AllArgsConstructor
+  @ToString
+  class AddImplRequest implements FunctionUpdateRequest {
+
+    @Getter
+    @JsonProperty("parameters")
+    private final FunctionParamDTO[] parameters;
+
+    @Getter
+    @JsonProperty("implementation")
+    private final FunctionImplDTO implementation;
+
+    @Override
+    public FunctionChange functionChange() {
+      FunctionParam[] params = new FunctionParam[parameters == null ? 0 : 
parameters.length];
+      if (parameters != null) {
+        for (int i = 0; i < parameters.length; i++) {
+          params[i] = parameters[i].toFunctionParam();
+        }
+      }
+      FunctionImpl impl = implementation.toFunctionImpl();
+      return FunctionChange.addImpl(params, impl);
+    }
+
+    @Override
+    public void validate() throws IllegalArgumentException {
+      Preconditions.checkArgument(
+          parameters != null, "\"parameters\" field is required and cannot be 
null");
+      Preconditions.checkArgument(
+          implementation != null, "\"implementation\" field is required and 
cannot be null");
+    }
+  }
+
+  /** The function update request for updating an implementation in a 
definition. */
+  @EqualsAndHashCode
+  @NoArgsConstructor(force = true)
+  @AllArgsConstructor
+  @ToString
+  class UpdateImplRequest implements FunctionUpdateRequest {
+
+    @Getter
+    @JsonProperty("parameters")
+    private final FunctionParamDTO[] parameters;
+
+    @Getter
+    @JsonProperty("runtime")
+    private final String runtime;
+
+    @Getter
+    @JsonProperty("implementation")
+    private final FunctionImplDTO implementation;
+
+    @Override
+    public FunctionChange functionChange() {
+      FunctionParam[] params = new FunctionParam[parameters == null ? 0 : 
parameters.length];
+      if (parameters != null) {
+        for (int i = 0; i < parameters.length; i++) {
+          params[i] = parameters[i].toFunctionParam();
+        }
+      }
+      FunctionImpl.RuntimeType runtimeType = 
FunctionImpl.RuntimeType.fromString(runtime);
+      FunctionImpl impl = implementation.toFunctionImpl();
+      return FunctionChange.updateImpl(params, runtimeType, impl);
+    }
+
+    @Override
+    public void validate() throws IllegalArgumentException {
+      Preconditions.checkArgument(
+          parameters != null, "\"parameters\" field is required and cannot be 
null");
+      Preconditions.checkArgument(
+          runtime != null, "\"runtime\" field is required and cannot be null");
+      Preconditions.checkArgument(
+          implementation != null, "\"implementation\" field is required and 
cannot be null");
+    }
+  }
+
+  /** The function update request for removing an implementation from a 
definition. */
+  @EqualsAndHashCode
+  @NoArgsConstructor(force = true)
+  @AllArgsConstructor
+  @ToString
+  class RemoveImplRequest implements FunctionUpdateRequest {
+
+    @Getter
+    @JsonProperty("parameters")
+    private final FunctionParamDTO[] parameters;
+
+    @Getter
+    @JsonProperty("runtime")
+    private final String runtime;
+
+    @Override
+    public FunctionChange functionChange() {
+      FunctionParam[] params = new FunctionParam[parameters == null ? 0 : 
parameters.length];
+      if (parameters != null) {
+        for (int i = 0; i < parameters.length; i++) {
+          params[i] = parameters[i].toFunctionParam();
+        }
+      }
+      FunctionImpl.RuntimeType runtimeType = 
FunctionImpl.RuntimeType.fromString(runtime);
+      return FunctionChange.removeImpl(params, runtimeType);
+    }
+
+    @Override
+    public void validate() throws IllegalArgumentException {
+      Preconditions.checkArgument(
+          parameters != null, "\"parameters\" field is required and cannot be 
null");
+      Preconditions.checkArgument(
+          runtime != null, "\"runtime\" field is required and cannot be null");
+    }
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/requests/FunctionUpdatesRequest.java
 
b/common/src/main/java/org/apache/gravitino/dto/requests/FunctionUpdatesRequest.java
new file mode 100644
index 0000000000..2f5468234f
--- /dev/null
+++ 
b/common/src/main/java/org/apache/gravitino/dto/requests/FunctionUpdatesRequest.java
@@ -0,0 +1,49 @@
+/*
+ * 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.gravitino.dto.requests;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.List;
+import lombok.AllArgsConstructor;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.ToString;
+import org.apache.gravitino.rest.RESTMessage;
+import org.apache.gravitino.rest.RESTRequest;
+
+/** Request to represent updates to a function. */
+@Getter
+@EqualsAndHashCode
+@NoArgsConstructor(force = true)
+@AllArgsConstructor
+@ToString
+public class FunctionUpdatesRequest implements RESTRequest {
+
+  @JsonProperty("updates")
+  private final List<FunctionUpdateRequest> updates;
+
+  @Override
+  public void validate() throws IllegalArgumentException {
+    if (updates == null) {
+      throw new IllegalArgumentException("Updates list cannot be null");
+    }
+    updates.forEach(RESTMessage::validate);
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/responses/FunctionListResponse.java
 
b/common/src/main/java/org/apache/gravitino/dto/responses/FunctionListResponse.java
new file mode 100644
index 0000000000..d9cbe2d727
--- /dev/null
+++ 
b/common/src/main/java/org/apache/gravitino/dto/responses/FunctionListResponse.java
@@ -0,0 +1,57 @@
+/*
+ * 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.gravitino.dto.responses;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import org.apache.gravitino.dto.function.FunctionDTO;
+
+/** Response wrapper for multiple functions. */
+@Getter
+@ToString
+@EqualsAndHashCode(callSuper = true)
+public class FunctionListResponse extends BaseResponse {
+
+  @JsonProperty("functions")
+  private FunctionDTO[] functions;
+
+  /**
+   * Creates a response containing multiple functions.
+   *
+   * @param functions Function array payload.
+   */
+  public FunctionListResponse(FunctionDTO[] functions) {
+    super(0);
+    this.functions = functions;
+  }
+
+  private FunctionListResponse() {
+    super();
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public void validate() throws IllegalArgumentException {
+    super.validate();
+    Preconditions.checkArgument(functions != null, "functions must not be 
null");
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/responses/FunctionResponse.java 
b/common/src/main/java/org/apache/gravitino/dto/responses/FunctionResponse.java
new file mode 100644
index 0000000000..0566aac300
--- /dev/null
+++ 
b/common/src/main/java/org/apache/gravitino/dto/responses/FunctionResponse.java
@@ -0,0 +1,70 @@
+/*
+ * 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.gravitino.dto.responses;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.base.Preconditions;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.ToString;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.dto.function.FunctionDTO;
+
+/** Response for function operations. */
+@Getter
+@ToString
+@EqualsAndHashCode(callSuper = true)
+public class FunctionResponse extends BaseResponse {
+
+  @JsonProperty("function")
+  private final FunctionDTO function;
+
+  /** Constructor for FunctionResponse. */
+  public FunctionResponse() {
+    super(0);
+    this.function = null;
+  }
+
+  /**
+   * Constructor for FunctionResponse.
+   *
+   * @param function the function DTO object.
+   */
+  public FunctionResponse(FunctionDTO function) {
+    super(0);
+    this.function = function;
+  }
+
+  /**
+   * Validates the response.
+   *
+   * @throws IllegalArgumentException if the response is invalid.
+   */
+  @Override
+  public void validate() throws IllegalArgumentException {
+    super.validate();
+    Preconditions.checkArgument(function != null, "function must not be null");
+    Preconditions.checkArgument(
+        StringUtils.isNotBlank(function.name()), "function 'name' must not be 
null and empty");
+    Preconditions.checkArgument(
+        function.functionType() != null, "function 'functionType' must not be 
null");
+    Preconditions.checkArgument(
+        function.definitions() != null, "function 'definitions' must not be 
null");
+  }
+}
diff --git 
a/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java 
b/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java
index 29ac24fec7..a5a8792b2c 100644
--- a/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java
+++ b/common/src/main/java/org/apache/gravitino/dto/util/DTOConverters.java
@@ -51,6 +51,9 @@ import org.apache.gravitino.dto.authorization.UserDTO;
 import org.apache.gravitino.dto.credential.CredentialDTO;
 import org.apache.gravitino.dto.file.FileInfoDTO;
 import org.apache.gravitino.dto.file.FilesetDTO;
+import org.apache.gravitino.dto.function.FunctionColumnDTO;
+import org.apache.gravitino.dto.function.FunctionDTO;
+import org.apache.gravitino.dto.function.FunctionDefinitionDTO;
 import org.apache.gravitino.dto.job.JobTemplateDTO;
 import org.apache.gravitino.dto.job.ShellJobTemplateDTO;
 import org.apache.gravitino.dto.job.SparkJobTemplateDTO;
@@ -88,6 +91,7 @@ import org.apache.gravitino.dto.tag.MetadataObjectDTO;
 import org.apache.gravitino.dto.tag.TagDTO;
 import org.apache.gravitino.file.FileInfo;
 import org.apache.gravitino.file.Fileset;
+import org.apache.gravitino.function.Function;
 import org.apache.gravitino.job.JobTemplate;
 import org.apache.gravitino.job.ShellJobTemplate;
 import org.apache.gravitino.job.SparkJobTemplate;
@@ -726,6 +730,41 @@ public class DTOConverters {
         .build();
   }
 
+  /**
+   * Converts a Function to a FunctionDTO.
+   *
+   * @param function The function to be converted.
+   * @return The function DTO.
+   */
+  public static FunctionDTO toDTO(Function function) {
+    FunctionColumnDTO[] returnColumnDTOs = null;
+    if (function.returnColumns() != null && function.returnColumns().length > 
0) {
+      returnColumnDTOs =
+          Arrays.stream(function.returnColumns())
+              .map(FunctionColumnDTO::fromFunctionColumn)
+              .toArray(FunctionColumnDTO[]::new);
+    }
+
+    FunctionDefinitionDTO[] definitionDTOs = null;
+    if (function.definitions() != null) {
+      definitionDTOs =
+          Arrays.stream(function.definitions())
+              .map(FunctionDefinitionDTO::fromFunctionDefinition)
+              .toArray(FunctionDefinitionDTO[]::new);
+    }
+
+    return FunctionDTO.builder()
+        .withName(function.name())
+        .withFunctionType(function.functionType())
+        .withDeterministic(function.deterministic())
+        .withComment(function.comment())
+        .withReturnType(function.returnType())
+        .withReturnColumns(returnColumnDTOs)
+        .withDefinitions(definitionDTOs)
+        .withAudit(toDTO(function.auditInfo()))
+        .build();
+  }
+
   /**
    * Converts an array of ModelVersions to an array of ModelVersionDTOs.
    *
diff --git 
a/common/src/test/java/org/apache/gravitino/dto/function/TestFunctionDTO.java 
b/common/src/test/java/org/apache/gravitino/dto/function/TestFunctionDTO.java
new file mode 100644
index 0000000000..be49db901a
--- /dev/null
+++ 
b/common/src/test/java/org/apache/gravitino/dto/function/TestFunctionDTO.java
@@ -0,0 +1,260 @@
+/*
+ * 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.gravitino.dto.function;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.common.collect.ImmutableMap;
+import java.time.Instant;
+import java.util.Map;
+import org.apache.gravitino.dto.AuditDTO;
+import org.apache.gravitino.dto.rel.expressions.LiteralDTO;
+import org.apache.gravitino.function.FunctionImpl;
+import org.apache.gravitino.function.FunctionType;
+import org.apache.gravitino.json.JsonUtils;
+import org.apache.gravitino.rel.types.Types;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestFunctionDTO {
+
+  @Test
+  public void testFunctionDTOSerDe() throws JsonProcessingException {
+    AuditDTO audit = 
AuditDTO.builder().withCreator("user1").withCreateTime(Instant.now()).build();
+
+    // Create function parameter with default value
+    FunctionParamDTO param1 =
+        FunctionParamDTO.builder()
+            .withName("x")
+            .withDataType(Types.IntegerType.get())
+            .withComment("input parameter")
+            .build();
+
+    FunctionParamDTO param2 =
+        FunctionParamDTO.builder()
+            .withName("y")
+            .withDataType(Types.FloatType.get())
+            .withDefaultValue(
+                
LiteralDTO.builder().withDataType(Types.FloatType.get()).withValue("1.0").build())
+            .build();
+
+    // Create SQL implementation
+    SQLImplDTO sqlImpl =
+        new SQLImplDTO(
+            FunctionImpl.RuntimeType.SPARK.name(),
+            null,
+            ImmutableMap.of("key", "value"),
+            "SELECT x + y");
+
+    // Create function definition
+    FunctionDefinitionDTO definition =
+        FunctionDefinitionDTO.builder()
+            .withParameters(new FunctionParamDTO[] {param1, param2})
+            .withImpls(new FunctionImplDTO[] {sqlImpl})
+            .build();
+
+    // Create scalar function DTO
+    FunctionDTO scalarFunction =
+        FunctionDTO.builder()
+            .withName("add_func")
+            .withFunctionType(FunctionType.SCALAR)
+            .withDeterministic(true)
+            .withComment("A simple add function")
+            .withReturnType(Types.FloatType.get())
+            .withDefinitions(new FunctionDefinitionDTO[] {definition})
+            .withAudit(audit)
+            .build();
+
+    // Serialize and deserialize
+    String json = JsonUtils.objectMapper().writeValueAsString(scalarFunction);
+    FunctionDTO deserialized = JsonUtils.objectMapper().readValue(json, 
FunctionDTO.class);
+
+    Assertions.assertEquals(scalarFunction.name(), deserialized.name());
+    Assertions.assertEquals(scalarFunction.functionType(), 
deserialized.functionType());
+    Assertions.assertEquals(scalarFunction.deterministic(), 
deserialized.deterministic());
+    Assertions.assertEquals(scalarFunction.comment(), deserialized.comment());
+    Assertions.assertEquals(scalarFunction.returnType(), 
deserialized.returnType());
+    Assertions.assertEquals(scalarFunction.definitions().length, 
deserialized.definitions().length);
+  }
+
+  @Test
+  public void testTableFunctionDTOSerDe() throws JsonProcessingException {
+    AuditDTO audit = 
AuditDTO.builder().withCreator("user1").withCreateTime(Instant.now()).build();
+
+    // Create return columns for table function
+    FunctionColumnDTO col1 =
+        FunctionColumnDTO.builder()
+            .withName("id")
+            .withDataType(Types.IntegerType.get())
+            .withComment("id column")
+            .build();
+
+    FunctionColumnDTO col2 =
+        
FunctionColumnDTO.builder().withName("name").withDataType(Types.StringType.get()).build();
+
+    // Create Java implementation
+    JavaImplDTO javaImpl =
+        new JavaImplDTO(
+            FunctionImpl.RuntimeType.SPARK.name(),
+            FunctionResourcesDTO.builder()
+                .withJars(new String[] {"hdfs://path/to/jar.jar"})
+                .build(),
+            ImmutableMap.of(),
+            "com.example.TableFunction");
+
+    // Create function definition
+    FunctionDefinitionDTO definition =
+        FunctionDefinitionDTO.builder()
+            .withParameters(new FunctionParamDTO[] {})
+            .withImpls(new FunctionImplDTO[] {javaImpl})
+            .build();
+
+    // Create table function DTO
+    FunctionDTO tableFunction =
+        FunctionDTO.builder()
+            .withName("table_func")
+            .withFunctionType(FunctionType.TABLE)
+            .withDeterministic(false)
+            .withComment("A table function")
+            .withReturnColumns(new FunctionColumnDTO[] {col1, col2})
+            .withDefinitions(new FunctionDefinitionDTO[] {definition})
+            .withAudit(audit)
+            .build();
+
+    // Serialize and deserialize
+    String json = JsonUtils.objectMapper().writeValueAsString(tableFunction);
+    FunctionDTO deserialized = JsonUtils.objectMapper().readValue(json, 
FunctionDTO.class);
+
+    Assertions.assertEquals(tableFunction.name(), deserialized.name());
+    Assertions.assertEquals(FunctionType.TABLE, deserialized.functionType());
+    Assertions.assertEquals(2, deserialized.returnColumns().length);
+  }
+
+  @Test
+  public void testFunctionImplDTOSerDe() throws JsonProcessingException {
+    Map<String, String> props = ImmutableMap.of("key", "value");
+    FunctionResourcesDTO resources =
+        FunctionResourcesDTO.builder()
+            .withJars(new String[] {"hdfs://path/to/jar.jar"})
+            .withFiles(new String[] {"hdfs://path/to/file.txt"})
+            .build();
+
+    // Test SQL implementation
+    SQLImplDTO sqlImpl = new SQLImplDTO("SPARK", resources, props, "SELECT 1");
+    String sqlJson = JsonUtils.objectMapper().writeValueAsString(sqlImpl);
+    FunctionImplDTO deserializedSql =
+        JsonUtils.objectMapper().readValue(sqlJson, FunctionImplDTO.class);
+    Assertions.assertTrue(deserializedSql instanceof SQLImplDTO);
+    Assertions.assertEquals(FunctionImpl.Language.SQL, 
deserializedSql.language());
+    Assertions.assertEquals("SELECT 1", ((SQLImplDTO) 
deserializedSql).getSql());
+
+    // Test Java implementation
+    JavaImplDTO javaImpl = new JavaImplDTO("SPARK", resources, props, 
"com.example.MyUDF");
+    String javaJson = JsonUtils.objectMapper().writeValueAsString(javaImpl);
+    FunctionImplDTO deserializedJava =
+        JsonUtils.objectMapper().readValue(javaJson, FunctionImplDTO.class);
+    Assertions.assertTrue(deserializedJava instanceof JavaImplDTO);
+    Assertions.assertEquals(FunctionImpl.Language.JAVA, 
deserializedJava.language());
+    Assertions.assertEquals("com.example.MyUDF", ((JavaImplDTO) 
deserializedJava).getClassName());
+
+    // Test Python implementation
+    PythonImplDTO pythonImpl =
+        new PythonImplDTO(
+            "SPARK", resources, props, "my_handler", "def my_handler(x): 
return x * 2");
+    String pythonJson = 
JsonUtils.objectMapper().writeValueAsString(pythonImpl);
+    FunctionImplDTO deserializedPython =
+        JsonUtils.objectMapper().readValue(pythonJson, FunctionImplDTO.class);
+    Assertions.assertTrue(deserializedPython instanceof PythonImplDTO);
+    Assertions.assertEquals(FunctionImpl.Language.PYTHON, 
deserializedPython.language());
+    Assertions.assertEquals("my_handler", ((PythonImplDTO) 
deserializedPython).getHandler());
+  }
+
+  @Test
+  public void testFunctionParamDTOWithDefaultValue() throws 
JsonProcessingException {
+    // Test parameter without default value
+    FunctionParamDTO paramWithoutDefault =
+        FunctionParamDTO.builder()
+            .withName("x")
+            .withDataType(Types.IntegerType.get())
+            .withComment("no default")
+            .build();
+
+    String json1 = 
JsonUtils.objectMapper().writeValueAsString(paramWithoutDefault);
+    FunctionParamDTO deserialized1 =
+        JsonUtils.objectMapper().readValue(json1, FunctionParamDTO.class);
+    Assertions.assertEquals("x", deserialized1.name());
+    Assertions.assertEquals(Types.IntegerType.get(), deserialized1.dataType());
+
+    // Test parameter with default value
+    LiteralDTO defaultValue =
+        
LiteralDTO.builder().withDataType(Types.IntegerType.get()).withValue("10").build();
+
+    FunctionParamDTO paramWithDefault =
+        FunctionParamDTO.builder()
+            .withName("y")
+            .withDataType(Types.IntegerType.get())
+            .withDefaultValue(defaultValue)
+            .build();
+
+    String json2 = 
JsonUtils.objectMapper().writeValueAsString(paramWithDefault);
+    FunctionParamDTO deserialized2 =
+        JsonUtils.objectMapper().readValue(json2, FunctionParamDTO.class);
+    Assertions.assertEquals("y", deserialized2.name());
+    Assertions.assertNotNull(deserialized2.defaultValue());
+  }
+
+  @Test
+  public void testFunctionResourcesDTOSerDe() throws JsonProcessingException {
+    FunctionResourcesDTO resources =
+        FunctionResourcesDTO.builder()
+            .withJars(new String[] {"hdfs://path/to/jar1.jar", 
"hdfs://path/to/jar2.jar"})
+            .withFiles(new String[] {"hdfs://path/to/file.txt"})
+            .withArchives(new String[] {"hdfs://path/to/archive.zip"})
+            .build();
+
+    String json = JsonUtils.objectMapper().writeValueAsString(resources);
+    FunctionResourcesDTO deserialized =
+        JsonUtils.objectMapper().readValue(json, FunctionResourcesDTO.class);
+
+    Assertions.assertArrayEquals(resources.getJars(), deserialized.getJars());
+    Assertions.assertArrayEquals(resources.getFiles(), 
deserialized.getFiles());
+    Assertions.assertArrayEquals(resources.getArchives(), 
deserialized.getArchives());
+  }
+
+  @Test
+  public void testFunctionDefinitionDTOSerDe() throws JsonProcessingException {
+    FunctionParamDTO param =
+        
FunctionParamDTO.builder().withName("input").withDataType(Types.StringType.get()).build();
+
+    SQLImplDTO impl = new SQLImplDTO("SPARK", null, null, "SELECT 
UPPER(input)");
+
+    FunctionDefinitionDTO definition =
+        FunctionDefinitionDTO.builder()
+            .withParameters(new FunctionParamDTO[] {param})
+            .withImpls(new FunctionImplDTO[] {impl})
+            .build();
+
+    String json = JsonUtils.objectMapper().writeValueAsString(definition);
+    FunctionDefinitionDTO deserialized =
+        JsonUtils.objectMapper().readValue(json, FunctionDefinitionDTO.class);
+
+    Assertions.assertEquals(1, deserialized.parameters().length);
+    Assertions.assertEquals("input", deserialized.parameters()[0].name());
+    Assertions.assertEquals(1, deserialized.impls().length);
+  }
+}
diff --git 
a/common/src/test/java/org/apache/gravitino/dto/requests/TestFunctionRegisterRequest.java
 
b/common/src/test/java/org/apache/gravitino/dto/requests/TestFunctionRegisterRequest.java
new file mode 100644
index 0000000000..aaced9d58e
--- /dev/null
+++ 
b/common/src/test/java/org/apache/gravitino/dto/requests/TestFunctionRegisterRequest.java
@@ -0,0 +1,179 @@
+/*
+ * 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.gravitino.dto.requests;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.apache.gravitino.dto.function.FunctionColumnDTO;
+import org.apache.gravitino.dto.function.FunctionDefinitionDTO;
+import org.apache.gravitino.dto.function.FunctionParamDTO;
+import org.apache.gravitino.dto.function.SQLImplDTO;
+import org.apache.gravitino.function.FunctionImpl;
+import org.apache.gravitino.function.FunctionType;
+import org.apache.gravitino.json.JsonUtils;
+import org.apache.gravitino.rel.types.Types;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestFunctionRegisterRequest {
+
+  @Test
+  public void testFunctionRegisterRequestSerDe() throws 
JsonProcessingException {
+    FunctionParamDTO param =
+        
FunctionParamDTO.builder().withName("x").withDataType(Types.IntegerType.get()).build();
+    SQLImplDTO impl =
+        new SQLImplDTO(FunctionImpl.RuntimeType.SPARK.name(), null, null, 
"SELECT x + 1");
+    FunctionDefinitionDTO definition =
+        FunctionDefinitionDTO.builder()
+            .withParameters(new FunctionParamDTO[] {param})
+            .withImpls(new SQLImplDTO[] {impl})
+            .build();
+
+    FunctionRegisterRequest request =
+        FunctionRegisterRequest.builder()
+            .withName("test_func")
+            .withFunctionType(FunctionType.SCALAR)
+            .withDeterministic(true)
+            .withComment("test function")
+            .withReturnType(Types.IntegerType.get())
+            .withDefinitions(new FunctionDefinitionDTO[] {definition})
+            .build();
+
+    String json = JsonUtils.objectMapper().writeValueAsString(request);
+    FunctionRegisterRequest deserialized =
+        JsonUtils.objectMapper().readValue(json, 
FunctionRegisterRequest.class);
+
+    Assertions.assertEquals(request, deserialized);
+  }
+
+  @Test
+  public void testValidateScalarFunction() {
+    FunctionDefinitionDTO definition =
+        FunctionDefinitionDTO.builder().withParameters(new 
FunctionParamDTO[0]).build();
+
+    FunctionRegisterRequest validRequest =
+        FunctionRegisterRequest.builder()
+            .withName("scalar_func")
+            .withFunctionType(FunctionType.SCALAR)
+            .withDeterministic(true)
+            .withReturnType(Types.IntegerType.get())
+            .withDefinitions(new FunctionDefinitionDTO[] {definition})
+            .build();
+
+    Assertions.assertDoesNotThrow(validRequest::validate);
+
+    // Missing returnType for SCALAR function
+    FunctionRegisterRequest invalidRequest =
+        FunctionRegisterRequest.builder()
+            .withName("scalar_func")
+            .withFunctionType(FunctionType.SCALAR)
+            .withDeterministic(true)
+            .withDefinitions(new FunctionDefinitionDTO[] {definition})
+            .build();
+
+    Assertions.assertThrows(IllegalArgumentException.class, 
invalidRequest::validate);
+  }
+
+  @Test
+  public void testValidateAggregateFunction() {
+    FunctionDefinitionDTO definition =
+        FunctionDefinitionDTO.builder().withParameters(new 
FunctionParamDTO[0]).build();
+
+    FunctionRegisterRequest validRequest =
+        FunctionRegisterRequest.builder()
+            .withName("agg_func")
+            .withFunctionType(FunctionType.AGGREGATE)
+            .withDeterministic(true)
+            .withReturnType(Types.IntegerType.get())
+            .withDefinitions(new FunctionDefinitionDTO[] {definition})
+            .build();
+
+    Assertions.assertDoesNotThrow(validRequest::validate);
+
+    // Missing returnType for AGGREGATE function
+    FunctionRegisterRequest invalidRequest =
+        FunctionRegisterRequest.builder()
+            .withName("agg_func")
+            .withFunctionType(FunctionType.AGGREGATE)
+            .withDeterministic(true)
+            .withDefinitions(new FunctionDefinitionDTO[] {definition})
+            .build();
+
+    Assertions.assertThrows(IllegalArgumentException.class, 
invalidRequest::validate);
+  }
+
+  @Test
+  public void testValidateTableFunction() {
+    FunctionDefinitionDTO definition =
+        FunctionDefinitionDTO.builder().withParameters(new 
FunctionParamDTO[0]).build();
+
+    FunctionColumnDTO col =
+        
FunctionColumnDTO.builder().withName("id").withDataType(Types.IntegerType.get()).build();
+
+    FunctionRegisterRequest validRequest =
+        FunctionRegisterRequest.builder()
+            .withName("table_func")
+            .withFunctionType(FunctionType.TABLE)
+            .withDeterministic(false)
+            .withReturnColumns(new FunctionColumnDTO[] {col})
+            .withDefinitions(new FunctionDefinitionDTO[] {definition})
+            .build();
+
+    Assertions.assertDoesNotThrow(validRequest::validate);
+
+    // Missing returnColumns for TABLE function
+    FunctionRegisterRequest invalidRequest =
+        FunctionRegisterRequest.builder()
+            .withName("table_func")
+            .withFunctionType(FunctionType.TABLE)
+            .withDeterministic(false)
+            .withDefinitions(new FunctionDefinitionDTO[] {definition})
+            .build();
+
+    Assertions.assertThrows(IllegalArgumentException.class, 
invalidRequest::validate);
+  }
+
+  @Test
+  public void testValidateMissingName() {
+    FunctionDefinitionDTO definition =
+        FunctionDefinitionDTO.builder().withParameters(new 
FunctionParamDTO[0]).build();
+
+    FunctionRegisterRequest invalidRequest =
+        FunctionRegisterRequest.builder()
+            .withFunctionType(FunctionType.SCALAR)
+            .withDeterministic(true)
+            .withReturnType(Types.IntegerType.get())
+            .withDefinitions(new FunctionDefinitionDTO[] {definition})
+            .build();
+
+    Assertions.assertThrows(IllegalArgumentException.class, 
invalidRequest::validate);
+  }
+
+  @Test
+  public void testValidateMissingDefinitions() {
+    FunctionRegisterRequest invalidRequest =
+        FunctionRegisterRequest.builder()
+            .withName("test_func")
+            .withFunctionType(FunctionType.SCALAR)
+            .withDeterministic(true)
+            .withReturnType(Types.IntegerType.get())
+            .build();
+
+    Assertions.assertThrows(IllegalArgumentException.class, 
invalidRequest::validate);
+  }
+}
diff --git 
a/common/src/test/java/org/apache/gravitino/dto/requests/TestFunctionUpdateRequest.java
 
b/common/src/test/java/org/apache/gravitino/dto/requests/TestFunctionUpdateRequest.java
new file mode 100644
index 0000000000..cfd49a0e02
--- /dev/null
+++ 
b/common/src/test/java/org/apache/gravitino/dto/requests/TestFunctionUpdateRequest.java
@@ -0,0 +1,253 @@
+/*
+ * 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.gravitino.dto.requests;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.apache.gravitino.dto.function.FunctionDefinitionDTO;
+import org.apache.gravitino.dto.function.FunctionImplDTO;
+import org.apache.gravitino.dto.function.FunctionParamDTO;
+import org.apache.gravitino.dto.function.SQLImplDTO;
+import org.apache.gravitino.function.FunctionChange;
+import org.apache.gravitino.function.FunctionImpl;
+import org.apache.gravitino.json.JsonUtils;
+import org.apache.gravitino.rel.types.Types;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestFunctionUpdateRequest {
+
+  @Test
+  public void testUpdateCommentRequestSerDe() throws JsonProcessingException {
+    FunctionUpdateRequest.UpdateCommentRequest request =
+        new FunctionUpdateRequest.UpdateCommentRequest("new comment");
+
+    String json = JsonUtils.objectMapper().writeValueAsString(request);
+    FunctionUpdateRequest deserialized =
+        JsonUtils.objectMapper().readValue(json, FunctionUpdateRequest.class);
+
+    
Assertions.assertInstanceOf(FunctionUpdateRequest.UpdateCommentRequest.class, 
deserialized);
+    Assertions.assertEquals(request, deserialized);
+  }
+
+  @Test
+  public void testUpdateCommentRequestValidate() {
+    // null comment is allowed (to clear comment)
+    FunctionUpdateRequest.UpdateCommentRequest request =
+        new FunctionUpdateRequest.UpdateCommentRequest(null);
+    Assertions.assertDoesNotThrow(request::validate);
+
+    // non-null comment is also valid
+    FunctionUpdateRequest.UpdateCommentRequest request2 =
+        new FunctionUpdateRequest.UpdateCommentRequest("new comment");
+    Assertions.assertDoesNotThrow(request2::validate);
+  }
+
+  @Test
+  public void testUpdateCommentRequestFunctionChange() {
+    FunctionUpdateRequest.UpdateCommentRequest request =
+        new FunctionUpdateRequest.UpdateCommentRequest("new comment");
+    FunctionChange change = request.functionChange();
+    Assertions.assertNotNull(change);
+  }
+
+  @Test
+  public void testAddDefinitionRequestSerDe() throws JsonProcessingException {
+    FunctionParamDTO param =
+        
FunctionParamDTO.builder().withName("x").withDataType(Types.IntegerType.get()).build();
+    SQLImplDTO impl = new SQLImplDTO(FunctionImpl.RuntimeType.SPARK.name(), 
null, null, "SELECT x");
+    FunctionDefinitionDTO definition =
+        FunctionDefinitionDTO.builder()
+            .withParameters(new FunctionParamDTO[] {param})
+            .withImpls(new FunctionImplDTO[] {impl})
+            .build();
+
+    FunctionUpdateRequest.AddDefinitionRequest request =
+        new FunctionUpdateRequest.AddDefinitionRequest(definition);
+
+    String json = JsonUtils.objectMapper().writeValueAsString(request);
+    FunctionUpdateRequest deserialized =
+        JsonUtils.objectMapper().readValue(json, FunctionUpdateRequest.class);
+
+    
Assertions.assertInstanceOf(FunctionUpdateRequest.AddDefinitionRequest.class, 
deserialized);
+    Assertions.assertEquals(request, deserialized);
+  }
+
+  @Test
+  public void testAddDefinitionRequestValidate() {
+    // Valid request
+    FunctionDefinitionDTO definition =
+        FunctionDefinitionDTO.builder().withParameters(new 
FunctionParamDTO[0]).build();
+    FunctionUpdateRequest.AddDefinitionRequest validRequest =
+        new FunctionUpdateRequest.AddDefinitionRequest(definition);
+    Assertions.assertDoesNotThrow(validRequest::validate);
+
+    // Invalid request - null definition
+    FunctionUpdateRequest.AddDefinitionRequest invalidRequest =
+        new FunctionUpdateRequest.AddDefinitionRequest(null);
+    Assertions.assertThrows(IllegalArgumentException.class, 
invalidRequest::validate);
+  }
+
+  @Test
+  public void testRemoveDefinitionRequestSerDe() throws 
JsonProcessingException {
+    FunctionParamDTO param =
+        
FunctionParamDTO.builder().withName("x").withDataType(Types.IntegerType.get()).build();
+
+    FunctionUpdateRequest.RemoveDefinitionRequest request =
+        new FunctionUpdateRequest.RemoveDefinitionRequest(new 
FunctionParamDTO[] {param});
+
+    String json = JsonUtils.objectMapper().writeValueAsString(request);
+    FunctionUpdateRequest deserialized =
+        JsonUtils.objectMapper().readValue(json, FunctionUpdateRequest.class);
+
+    
Assertions.assertInstanceOf(FunctionUpdateRequest.RemoveDefinitionRequest.class,
 deserialized);
+    Assertions.assertEquals(request, deserialized);
+  }
+
+  @Test
+  public void testRemoveDefinitionRequestValidate() {
+    // Valid request
+    FunctionUpdateRequest.RemoveDefinitionRequest validRequest =
+        new FunctionUpdateRequest.RemoveDefinitionRequest(new 
FunctionParamDTO[0]);
+    Assertions.assertDoesNotThrow(validRequest::validate);
+
+    // Invalid request - null parameters
+    FunctionUpdateRequest.RemoveDefinitionRequest invalidRequest =
+        new FunctionUpdateRequest.RemoveDefinitionRequest(null);
+    Assertions.assertThrows(IllegalArgumentException.class, 
invalidRequest::validate);
+  }
+
+  @Test
+  public void testAddImplRequestSerDe() throws JsonProcessingException {
+    FunctionParamDTO param =
+        
FunctionParamDTO.builder().withName("x").withDataType(Types.IntegerType.get()).build();
+    SQLImplDTO impl = new SQLImplDTO(FunctionImpl.RuntimeType.SPARK.name(), 
null, null, "SELECT x");
+
+    FunctionUpdateRequest.AddImplRequest request =
+        new FunctionUpdateRequest.AddImplRequest(new FunctionParamDTO[] 
{param}, impl);
+
+    String json = JsonUtils.objectMapper().writeValueAsString(request);
+    FunctionUpdateRequest deserialized =
+        JsonUtils.objectMapper().readValue(json, FunctionUpdateRequest.class);
+
+    Assertions.assertInstanceOf(FunctionUpdateRequest.AddImplRequest.class, 
deserialized);
+    Assertions.assertEquals(request, deserialized);
+  }
+
+  @Test
+  public void testAddImplRequestValidate() {
+    SQLImplDTO impl = new SQLImplDTO(FunctionImpl.RuntimeType.SPARK.name(), 
null, null, "SELECT 1");
+
+    // Valid request
+    FunctionUpdateRequest.AddImplRequest validRequest =
+        new FunctionUpdateRequest.AddImplRequest(new FunctionParamDTO[0], 
impl);
+    Assertions.assertDoesNotThrow(validRequest::validate);
+
+    // Invalid request - null parameters
+    FunctionUpdateRequest.AddImplRequest invalidRequest1 =
+        new FunctionUpdateRequest.AddImplRequest(null, impl);
+    Assertions.assertThrows(IllegalArgumentException.class, 
invalidRequest1::validate);
+
+    // Invalid request - null implementation
+    FunctionUpdateRequest.AddImplRequest invalidRequest2 =
+        new FunctionUpdateRequest.AddImplRequest(new FunctionParamDTO[0], 
null);
+    Assertions.assertThrows(IllegalArgumentException.class, 
invalidRequest2::validate);
+  }
+
+  @Test
+  public void testUpdateImplRequestSerDe() throws JsonProcessingException {
+    FunctionParamDTO param =
+        
FunctionParamDTO.builder().withName("x").withDataType(Types.IntegerType.get()).build();
+    SQLImplDTO impl = new SQLImplDTO(FunctionImpl.RuntimeType.SPARK.name(), 
null, null, "SELECT x");
+
+    FunctionUpdateRequest.UpdateImplRequest request =
+        new FunctionUpdateRequest.UpdateImplRequest(
+            new FunctionParamDTO[] {param}, 
FunctionImpl.RuntimeType.SPARK.name(), impl);
+
+    String json = JsonUtils.objectMapper().writeValueAsString(request);
+    FunctionUpdateRequest deserialized =
+        JsonUtils.objectMapper().readValue(json, FunctionUpdateRequest.class);
+
+    Assertions.assertInstanceOf(FunctionUpdateRequest.UpdateImplRequest.class, 
deserialized);
+    Assertions.assertEquals(request, deserialized);
+  }
+
+  @Test
+  public void testUpdateImplRequestValidate() {
+    SQLImplDTO impl = new SQLImplDTO(FunctionImpl.RuntimeType.SPARK.name(), 
null, null, "SELECT 1");
+
+    // Valid request
+    FunctionUpdateRequest.UpdateImplRequest validRequest =
+        new FunctionUpdateRequest.UpdateImplRequest(
+            new FunctionParamDTO[0], FunctionImpl.RuntimeType.SPARK.name(), 
impl);
+    Assertions.assertDoesNotThrow(validRequest::validate);
+
+    // Invalid request - null parameters
+    FunctionUpdateRequest.UpdateImplRequest invalidRequest1 =
+        new FunctionUpdateRequest.UpdateImplRequest(
+            null, FunctionImpl.RuntimeType.SPARK.name(), impl);
+    Assertions.assertThrows(IllegalArgumentException.class, 
invalidRequest1::validate);
+
+    // Invalid request - null runtime
+    FunctionUpdateRequest.UpdateImplRequest invalidRequest2 =
+        new FunctionUpdateRequest.UpdateImplRequest(new FunctionParamDTO[0], 
null, impl);
+    Assertions.assertThrows(IllegalArgumentException.class, 
invalidRequest2::validate);
+
+    // Invalid request - null implementation
+    FunctionUpdateRequest.UpdateImplRequest invalidRequest3 =
+        new FunctionUpdateRequest.UpdateImplRequest(
+            new FunctionParamDTO[0], FunctionImpl.RuntimeType.SPARK.name(), 
null);
+    Assertions.assertThrows(IllegalArgumentException.class, 
invalidRequest3::validate);
+  }
+
+  @Test
+  public void testRemoveImplRequestSerDe() throws JsonProcessingException {
+    FunctionParamDTO param =
+        
FunctionParamDTO.builder().withName("x").withDataType(Types.IntegerType.get()).build();
+
+    FunctionUpdateRequest.RemoveImplRequest request =
+        new FunctionUpdateRequest.RemoveImplRequest(
+            new FunctionParamDTO[] {param}, 
FunctionImpl.RuntimeType.SPARK.name());
+
+    String json = JsonUtils.objectMapper().writeValueAsString(request);
+    FunctionUpdateRequest deserialized =
+        JsonUtils.objectMapper().readValue(json, FunctionUpdateRequest.class);
+
+    Assertions.assertInstanceOf(FunctionUpdateRequest.RemoveImplRequest.class, 
deserialized);
+    Assertions.assertEquals(request, deserialized);
+  }
+
+  @Test
+  public void testRemoveImplRequestValidate() {
+    // Valid request
+    FunctionUpdateRequest.RemoveImplRequest validRequest =
+        new FunctionUpdateRequest.RemoveImplRequest(
+            new FunctionParamDTO[0], FunctionImpl.RuntimeType.SPARK.name());
+    Assertions.assertDoesNotThrow(validRequest::validate);
+
+    // Invalid request - null parameters
+    FunctionUpdateRequest.RemoveImplRequest invalidRequest1 =
+        new FunctionUpdateRequest.RemoveImplRequest(null, 
FunctionImpl.RuntimeType.SPARK.name());
+    Assertions.assertThrows(IllegalArgumentException.class, 
invalidRequest1::validate);
+
+    // Invalid request - null runtime
+    FunctionUpdateRequest.RemoveImplRequest invalidRequest2 =
+        new FunctionUpdateRequest.RemoveImplRequest(new FunctionParamDTO[0], 
null);
+    Assertions.assertThrows(IllegalArgumentException.class, 
invalidRequest2::validate);
+  }
+}
diff --git 
a/common/src/test/java/org/apache/gravitino/dto/requests/TestFunctionUpdatesRequest.java
 
b/common/src/test/java/org/apache/gravitino/dto/requests/TestFunctionUpdatesRequest.java
new file mode 100644
index 0000000000..9da5b2cedb
--- /dev/null
+++ 
b/common/src/test/java/org/apache/gravitino/dto/requests/TestFunctionUpdatesRequest.java
@@ -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.
+ */
+package org.apache.gravitino.dto.requests;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import java.util.Arrays;
+import java.util.Collections;
+import org.apache.gravitino.dto.function.FunctionDefinitionDTO;
+import org.apache.gravitino.dto.function.FunctionParamDTO;
+import org.apache.gravitino.json.JsonUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class TestFunctionUpdatesRequest {
+
+  @Test
+  public void testFunctionUpdatesRequestSerDe() throws JsonProcessingException 
{
+    FunctionUpdateRequest.UpdateCommentRequest updateComment =
+        new FunctionUpdateRequest.UpdateCommentRequest("new comment");
+    FunctionDefinitionDTO definition =
+        FunctionDefinitionDTO.builder().withParameters(new 
FunctionParamDTO[0]).build();
+    FunctionUpdateRequest.AddDefinitionRequest addDefinition =
+        new FunctionUpdateRequest.AddDefinitionRequest(definition);
+
+    FunctionUpdatesRequest request =
+        new FunctionUpdatesRequest(Arrays.asList(updateComment, 
addDefinition));
+
+    String json = JsonUtils.objectMapper().writeValueAsString(request);
+    FunctionUpdatesRequest deserialized =
+        JsonUtils.objectMapper().readValue(json, FunctionUpdatesRequest.class);
+
+    Assertions.assertEquals(request, deserialized);
+    Assertions.assertEquals(2, deserialized.getUpdates().size());
+  }
+
+  @Test
+  public void testValidate() {
+    FunctionUpdateRequest.UpdateCommentRequest updateComment =
+        new FunctionUpdateRequest.UpdateCommentRequest("comment");
+
+    // Valid request
+    FunctionUpdatesRequest validRequest =
+        new FunctionUpdatesRequest(Collections.singletonList(updateComment));
+    Assertions.assertDoesNotThrow(validRequest::validate);
+
+    // Empty list is valid
+    FunctionUpdatesRequest emptyRequest = new 
FunctionUpdatesRequest(Collections.emptyList());
+    Assertions.assertDoesNotThrow(emptyRequest::validate);
+
+    // Null list is invalid
+    FunctionUpdatesRequest invalidRequest = new FunctionUpdatesRequest(null);
+    Assertions.assertThrows(IllegalArgumentException.class, 
invalidRequest::validate);
+  }
+
+  @Test
+  public void testValidateWithInvalidUpdate() {
+    // Request with invalid inner request
+    FunctionUpdateRequest.AddDefinitionRequest invalidInner =
+        new FunctionUpdateRequest.AddDefinitionRequest(null);
+
+    FunctionUpdatesRequest request =
+        new FunctionUpdatesRequest(Collections.singletonList(invalidInner));
+
+    Assertions.assertThrows(IllegalArgumentException.class, request::validate);
+  }
+}


Reply via email to