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);
+ }
+}