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 1ed975bdb1 [#9525] feat(api): Add APIs for UDF operations (#9551)
1ed975bdb1 is described below
commit 1ed975bdb1a890828d7f3af1c14bd4b2691b6cf2
Author: mchades <[email protected]>
AuthorDate: Mon Jan 5 20:54:00 2026 +0800
[#9525] feat(api): Add APIs for UDF operations (#9551)
### What changes were proposed in this pull request?
Add APIs for UDF operations
### Why are the changes needed?
Fix: #9525
### Does this PR introduce _any_ user-facing change?
no
### How was this patch tested?
CI pass
---
.../main/java/org/apache/gravitino/Catalog.java | 9 +
.../exceptions/FunctionAlreadyExistsException.java | 50 +++
.../exceptions/NoSuchFunctionException.java | 49 +++
.../exceptions/NoSuchFunctionVersionException.java | 49 +++
.../org/apache/gravitino/function/Function.java | 106 ++++++
.../apache/gravitino/function/FunctionCatalog.java | 157 ++++++++
.../apache/gravitino/function/FunctionChange.java | 400 +++++++++++++++++++++
.../apache/gravitino/function/FunctionColumn.java | 110 ++++++
.../gravitino/function/FunctionDefinition.java | 40 +++
.../gravitino/function/FunctionDefinitions.java | 103 ++++++
.../apache/gravitino/function/FunctionImpl.java | 118 ++++++
.../apache/gravitino/function/FunctionImpls.java | 125 +++++++
.../apache/gravitino/function/FunctionParam.java | 57 +++
.../apache/gravitino/function/FunctionParams.java | 153 ++++++++
.../gravitino/function/FunctionResources.java | 112 ++++++
.../apache/gravitino/function/FunctionType.java | 73 ++++
.../org/apache/gravitino/function/JavaImpl.java | 87 +++++
.../org/apache/gravitino/function/PythonImpl.java | 101 ++++++
.../org/apache/gravitino/function/SQLImpl.java | 86 +++++
19 files changed, 1985 insertions(+)
diff --git a/api/src/main/java/org/apache/gravitino/Catalog.java
b/api/src/main/java/org/apache/gravitino/Catalog.java
index 680c4c6b1a..ed63285e95 100644
--- a/api/src/main/java/org/apache/gravitino/Catalog.java
+++ b/api/src/main/java/org/apache/gravitino/Catalog.java
@@ -24,6 +24,7 @@ import org.apache.gravitino.annotation.Evolving;
import org.apache.gravitino.authorization.SupportsRoles;
import org.apache.gravitino.credential.SupportsCredentials;
import org.apache.gravitino.file.FilesetCatalog;
+import org.apache.gravitino.function.FunctionCatalog;
import org.apache.gravitino.messaging.TopicCatalog;
import org.apache.gravitino.model.ModelCatalog;
import org.apache.gravitino.policy.SupportsPolicies;
@@ -237,6 +238,14 @@ public interface Catalog extends Auditable {
throw new UnsupportedOperationException("Catalog does not support model
operations");
}
+ /**
+ * @return the {@link FunctionCatalog} if the catalog supports function
operations.
+ * @throws UnsupportedOperationException if the catalog does not support
function operations.
+ */
+ default FunctionCatalog asFunctionCatalog() throws
UnsupportedOperationException {
+ throw new UnsupportedOperationException("Catalog does not support function
operations");
+ }
+
/**
* @return the {@link SupportsTags} if the catalog supports tag operations.
* @throws UnsupportedOperationException if the catalog does not support tag
operations.
diff --git
a/api/src/main/java/org/apache/gravitino/exceptions/FunctionAlreadyExistsException.java
b/api/src/main/java/org/apache/gravitino/exceptions/FunctionAlreadyExistsException.java
new file mode 100644
index 0000000000..9e2cf69d9a
--- /dev/null
+++
b/api/src/main/java/org/apache/gravitino/exceptions/FunctionAlreadyExistsException.java
@@ -0,0 +1,50 @@
+/*
+ * 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.exceptions;
+
+import com.google.errorprone.annotations.FormatMethod;
+import com.google.errorprone.annotations.FormatString;
+
+/** Exception thrown when a function with a specified name already exists. */
+public class FunctionAlreadyExistsException extends AlreadyExistsException {
+
+ /**
+ * Constructs a new exception with the specified detail message.
+ *
+ * @param message the detail message.
+ * @param args the arguments to the message.
+ */
+ @FormatMethod
+ public FunctionAlreadyExistsException(@FormatString String message,
Object... args) {
+ super(message, args);
+ }
+
+ /**
+ * Constructs a new exception with the specified detail message and cause.
+ *
+ * @param cause the cause.
+ * @param message the detail message.
+ * @param args the arguments to the message.
+ */
+ @FormatMethod
+ public FunctionAlreadyExistsException(
+ Throwable cause, @FormatString String message, Object... args) {
+ super(cause, message, args);
+ }
+}
diff --git
a/api/src/main/java/org/apache/gravitino/exceptions/NoSuchFunctionException.java
b/api/src/main/java/org/apache/gravitino/exceptions/NoSuchFunctionException.java
new file mode 100644
index 0000000000..97a2d64920
--- /dev/null
+++
b/api/src/main/java/org/apache/gravitino/exceptions/NoSuchFunctionException.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.exceptions;
+
+import com.google.errorprone.annotations.FormatMethod;
+import com.google.errorprone.annotations.FormatString;
+
+/** Exception thrown when a function with the specified name does not exist. */
+public class NoSuchFunctionException extends NotFoundException {
+
+ /**
+ * Constructs a new exception with the specified detail message.
+ *
+ * @param message the detail message.
+ * @param args the arguments to the message.
+ */
+ @FormatMethod
+ public NoSuchFunctionException(@FormatString String message, Object... args)
{
+ super(message, args);
+ }
+
+ /**
+ * Constructs a new exception with the specified detail message and cause.
+ *
+ * @param cause the cause.
+ * @param message the detail message.
+ * @param args the arguments to the message.
+ */
+ @FormatMethod
+ public NoSuchFunctionException(Throwable cause, String message, Object...
args) {
+ super(cause, message, args);
+ }
+}
diff --git
a/api/src/main/java/org/apache/gravitino/exceptions/NoSuchFunctionVersionException.java
b/api/src/main/java/org/apache/gravitino/exceptions/NoSuchFunctionVersionException.java
new file mode 100644
index 0000000000..7c0f3a22cc
--- /dev/null
+++
b/api/src/main/java/org/apache/gravitino/exceptions/NoSuchFunctionVersionException.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.exceptions;
+
+import com.google.errorprone.annotations.FormatMethod;
+import com.google.errorprone.annotations.FormatString;
+
+/** Exception thrown when a function with the specified version does not
exist. */
+public class NoSuchFunctionVersionException extends NotFoundException {
+
+ /**
+ * Constructs a new exception with the specified detail message.
+ *
+ * @param message the detail message.
+ * @param args the arguments to the message.
+ */
+ @FormatMethod
+ public NoSuchFunctionVersionException(@FormatString String message,
Object... args) {
+ super(message, args);
+ }
+
+ /**
+ * Constructs a new exception with the specified detail message and cause.
+ *
+ * @param cause the cause.
+ * @param message the detail message.
+ * @param args the arguments to the message.
+ */
+ @FormatMethod
+ public NoSuchFunctionVersionException(Throwable cause, String message,
Object... args) {
+ super(cause, message, args);
+ }
+}
diff --git a/api/src/main/java/org/apache/gravitino/function/Function.java
b/api/src/main/java/org/apache/gravitino/function/Function.java
new file mode 100644
index 0000000000..7634823adc
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/function/Function.java
@@ -0,0 +1,106 @@
+/*
+ * 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.function;
+
+import javax.annotation.Nullable;
+import org.apache.gravitino.Auditable;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.annotation.Evolving;
+import org.apache.gravitino.rel.types.Type;
+
+/**
+ * An interface representing a user-defined function under a schema {@link
Namespace}. A function is
+ * a reusable computational unit that can be invoked within queries across
different compute
+ * engines. Users can register a function in Gravitino to manage the function
metadata and enable
+ * cross-engine function sharing. The typical use case is to define custom
business logic once and
+ * reuse it across multiple compute engines like Spark, Trino, and AI engines.
+ *
+ * <p>A function is characterized by its name, type (scalar for row-by-row
operations, aggregate for
+ * group operations, or table-valued for set-returning operations), whether it
is deterministic, its
+ * return type or columns (for table function), and its definitions that
contain parameters and
+ * implementations for different runtime engines. Each function maintains a
version number starting
+ * from 0, which increments with each alteration.
+ */
+@Evolving
+public interface Function extends Auditable {
+ /** An empty array of {@link FunctionColumn}. */
+ FunctionColumn[] EMPTY = new FunctionColumn[0];
+
+ /**
+ * @return The function name.
+ */
+ String name();
+
+ /**
+ * @return The function type.
+ */
+ FunctionType functionType();
+
+ /**
+ * @return Whether the function is deterministic.
+ */
+ boolean deterministic();
+
+ /**
+ * @return The optional comment of the function.
+ */
+ @Nullable
+ default String comment() {
+ return null;
+ }
+
+ /**
+ * The return type for scalar or aggregate functions.
+ *
+ * @return The return type, null if this is a table-valued function.
+ */
+ @Nullable
+ default Type returnType() {
+ return null;
+ }
+
+ /**
+ * The output columns for a table-valued function.
+ *
+ * <p>A table-valued function is a function that returns a table instead of
a scalar value or an
+ * aggregate result. The returned table has a fixed schema defined by the
columns returned from
+ * this method.
+ *
+ * @return The output columns that define the schema of the table returned
by this function, or an
+ * empty array if this is a scalar or aggregate function.
+ */
+ default FunctionColumn[] returnColumns() {
+ return EMPTY;
+ }
+
+ /**
+ * @return The definitions of the function.
+ */
+ FunctionDefinition[] definitions();
+
+ /**
+ * Returns the internal revision version of the function.
+ *
+ * <p>This version is a 0-based counter, where {@code 0} represents the
initial definition of the
+ * function, and the value is incremented by 1 on each later alteration.
+ *
+ * @return The 0-based revision version of the function.
+ */
+ int version();
+}
diff --git
a/api/src/main/java/org/apache/gravitino/function/FunctionCatalog.java
b/api/src/main/java/org/apache/gravitino/function/FunctionCatalog.java
new file mode 100644
index 0000000000..73d9ce06ff
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/function/FunctionCatalog.java
@@ -0,0 +1,157 @@
+/*
+ * 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.function;
+
+import org.apache.gravitino.NameIdentifier;
+import org.apache.gravitino.Namespace;
+import org.apache.gravitino.annotation.Evolving;
+import org.apache.gravitino.exceptions.FunctionAlreadyExistsException;
+import org.apache.gravitino.exceptions.NoSuchFunctionException;
+import org.apache.gravitino.exceptions.NoSuchFunctionVersionException;
+import org.apache.gravitino.exceptions.NoSuchSchemaException;
+import org.apache.gravitino.rel.types.Type;
+
+/** The FunctionCatalog interface defines the public API for managing
functions in a schema. */
+@Evolving
+public interface FunctionCatalog {
+
+ /**
+ * List the functions in a namespace from the catalog.
+ *
+ * @param namespace A namespace.
+ * @return An array of function identifiers in the namespace.
+ * @throws NoSuchSchemaException If the schema does not exist.
+ */
+ NameIdentifier[] listFunctions(Namespace namespace) throws
NoSuchSchemaException;
+
+ /**
+ * List the functions with details in a namespace from the catalog.
+ *
+ * @param namespace A namespace.
+ * @return An array of functions in the namespace.
+ * @throws NoSuchSchemaException If the schema does not exist.
+ */
+ Function[] listFunctionInfos(Namespace namespace) throws
NoSuchSchemaException;
+
+ /**
+ * Get a function by {@link NameIdentifier} from the catalog. The identifier
only contains the
+ * schema and function name. A function may include multiple definitions
(overloads) in the
+ * result. This method returns the latest version of the function.
+ *
+ * @param ident A function identifier.
+ * @return The latest version of the function with the given name.
+ * @throws NoSuchFunctionException If the function does not exist.
+ */
+ Function getFunction(NameIdentifier ident) throws NoSuchFunctionException;
+
+ /**
+ * Get a function by {@link NameIdentifier} and version from the catalog.
The identifier only
+ * contains the schema and function name. A function may include multiple
definitions (overloads)
+ * in the result.
+ *
+ * @param ident A function identifier.
+ * @param version The zero-based function version index (0 for the first
created version), as
+ * returned by {@link Function#version()}.
+ * @return The function with the given name and version.
+ * @throws NoSuchFunctionException If the function does not exist.
+ * @throws NoSuchFunctionVersionException If the function version does not
exist.
+ */
+ Function getFunction(NameIdentifier ident, int version)
+ throws NoSuchFunctionException, NoSuchFunctionVersionException;
+
+ /**
+ * Check if a function with the given name exists in the catalog.
+ *
+ * @param ident The function identifier.
+ * @return True if the function exists, false otherwise.
+ */
+ default boolean functionExists(NameIdentifier ident) {
+ try {
+ getFunction(ident);
+ return true;
+ } catch (NoSuchFunctionException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Register a scalar or aggregate function with one or more definitions
(overloads).
+ *
+ * @param ident The function identifier.
+ * @param comment The optional function comment.
+ * @param functionType The function type.
+ * @param deterministic Whether the function is deterministic.
+ * @param returnType The return type.
+ * @param definitions The function definitions.
+ * @return The registered function.
+ * @throws NoSuchSchemaException If the schema does not exist.
+ * @throws FunctionAlreadyExistsException If the function already exists.
+ */
+ Function registerFunction(
+ NameIdentifier ident,
+ String comment,
+ FunctionType functionType,
+ boolean deterministic,
+ Type returnType,
+ FunctionDefinition[] definitions)
+ throws NoSuchSchemaException, FunctionAlreadyExistsException;
+
+ /**
+ * Register a table-valued function with one or more definitions (overloads).
+ *
+ * @param ident The function identifier.
+ * @param comment The optional function comment.
+ * @param deterministic Whether the function is deterministic.
+ * @param returnColumns The return columns.
+ * @param definitions The function definitions.
+ * @return The registered function.
+ * @throws NoSuchSchemaException If the schema does not exist.
+ * @throws FunctionAlreadyExistsException If the function already exists.
+ */
+ Function registerFunction(
+ NameIdentifier ident,
+ String comment,
+ boolean deterministic,
+ FunctionColumn[] returnColumns,
+ FunctionDefinition[] definitions)
+ throws NoSuchSchemaException, FunctionAlreadyExistsException;
+
+ /**
+ * Applies {@link FunctionChange changes} to a function in the catalog.
+ *
+ * <p>Implementations may reject the changes. If any change is rejected, no
changes should be
+ * applied to the function.
+ *
+ * @param ident the {@link NameIdentifier} instance of the function to alter.
+ * @param changes the several {@link FunctionChange} instances to apply to
the function.
+ * @return the updated {@link Function} instance.
+ * @throws NoSuchFunctionException If the function does not exist.
+ * @throws IllegalArgumentException If the change is rejected by the
implementation.
+ */
+ Function alterFunction(NameIdentifier ident, FunctionChange... changes)
+ throws NoSuchFunctionException, IllegalArgumentException;
+
+ /**
+ * Drop a function by name.
+ *
+ * @param ident The name identifier of the function.
+ * @return True if the function is deleted, false if the function does not
exist.
+ */
+ boolean dropFunction(NameIdentifier ident);
+}
diff --git
a/api/src/main/java/org/apache/gravitino/function/FunctionChange.java
b/api/src/main/java/org/apache/gravitino/function/FunctionChange.java
new file mode 100644
index 0000000000..5684cafca5
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/function/FunctionChange.java
@@ -0,0 +1,400 @@
+/*
+ * 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.function;
+
+import com.google.common.base.Preconditions;
+import java.util.Arrays;
+import java.util.Objects;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.annotation.Evolving;
+
+/** Represents a change that can be applied to a function. */
+@Evolving
+public interface FunctionChange {
+ /** An empty array of parameters. */
+ FunctionParam[] EMPTY_PARAMS = new FunctionParam[0];
+
+ /**
+ * Create a {@link FunctionChange} to update the comment of a function.
+ *
+ * @param newComment The new comment value.
+ * @return The change instance.
+ */
+ static FunctionChange updateComment(String newComment) {
+ return new UpdateComment(newComment);
+ }
+
+ /**
+ * Create a {@link FunctionChange} to add a new definition (overload) to a
function.
+ *
+ * @param definition The new definition to add.
+ * @return The change instance.
+ */
+ static FunctionChange addDefinition(FunctionDefinition definition) {
+ return new AddDefinition(definition);
+ }
+
+ /**
+ * Create a {@link FunctionChange} to remove an existing definition
(overload) from a function.
+ *
+ * @param parameters The parameters that identify the definition to remove.
+ * @return The change instance.
+ */
+ static FunctionChange removeDefinition(FunctionParam[] parameters) {
+ return new RemoveDefinition(parameters);
+ }
+
+ /**
+ * Create a {@link FunctionChange} to add an implementation to a specific
definition.
+ *
+ * @param parameters The parameters that identify the definition to update.
+ * @param implementation The implementation to add.
+ * @return The change instance.
+ */
+ static FunctionChange addImpl(FunctionParam[] parameters, FunctionImpl
implementation) {
+ return new AddImpl(parameters, implementation);
+ }
+
+ /**
+ * Create a {@link FunctionChange} to update an implementation for a
specific definition and
+ * runtime.
+ *
+ * @param parameters The parameters that identify the definition to update.
+ * @param runtime The runtime that identifies the implementation to replace.
+ * @param implementation The new implementation.
+ * @return The change instance.
+ */
+ static FunctionChange updateImpl(
+ FunctionParam[] parameters, FunctionImpl.RuntimeType runtime,
FunctionImpl implementation) {
+ return new UpdateImpl(parameters, runtime, implementation);
+ }
+
+ /**
+ * Create a {@link FunctionChange} to remove an implementation for a
specific definition and
+ * runtime.
+ *
+ * @param parameters The parameters that identify the definition to update.
+ * @param runtime The runtime that identifies the implementation to remove.
+ * @return The change instance.
+ */
+ static FunctionChange removeImpl(FunctionParam[] parameters,
FunctionImpl.RuntimeType runtime) {
+ return new RemoveImpl(parameters, runtime);
+ }
+
+ /** A {@link FunctionChange} to update the comment of a function. */
+ final class UpdateComment implements FunctionChange {
+ private final String newComment;
+
+ UpdateComment(String newComment) {
+ Preconditions.checkArgument(
+ StringUtils.isNotBlank(newComment), "New comment cannot be null or
empty");
+ this.newComment = newComment;
+ }
+
+ /**
+ * @return The new comment of the function.
+ */
+ public String newComment() {
+ return newComment;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof UpdateComment)) {
+ return false;
+ }
+ UpdateComment that = (UpdateComment) obj;
+ return Objects.equals(newComment, that.newComment);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(newComment);
+ }
+
+ @Override
+ public String toString() {
+ return "UpdateComment{newComment='" + newComment + "'}";
+ }
+ }
+
+ /** A {@link FunctionChange} to add a new definition to a function. */
+ final class AddDefinition implements FunctionChange {
+ private final FunctionDefinition definition;
+
+ AddDefinition(FunctionDefinition definition) {
+ Preconditions.checkArgument(definition != null, "Definition cannot be
null");
+ this.definition = definition;
+ }
+
+ /**
+ * @return The definition to add.
+ */
+ public FunctionDefinition definition() {
+ return definition;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof AddDefinition)) {
+ return false;
+ }
+ AddDefinition that = (AddDefinition) obj;
+ return Objects.equals(definition, that.definition);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(definition);
+ }
+
+ @Override
+ public String toString() {
+ return "AddDefinition{definition=" + definition + '}';
+ }
+ }
+
+ /** A {@link FunctionChange} to remove an existing definition from a
function. */
+ final class RemoveDefinition implements FunctionChange {
+ private final FunctionParam[] parameters;
+
+ RemoveDefinition(FunctionParam[] parameters) {
+ Preconditions.checkArgument(parameters != null, "Parameters cannot be
null");
+ this.parameters = Arrays.copyOf(parameters, parameters.length);
+ }
+
+ /**
+ * @return The parameters that identify the definition to remove.
+ */
+ public FunctionParam[] parameters() {
+ return parameters.length == 0 ? EMPTY_PARAMS : Arrays.copyOf(parameters,
parameters.length);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof RemoveDefinition)) {
+ return false;
+ }
+ RemoveDefinition that = (RemoveDefinition) obj;
+ return Arrays.equals(parameters, that.parameters);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(parameters);
+ }
+
+ @Override
+ public String toString() {
+ return "RemoveDefinition{parameters=" + Arrays.toString(parameters) +
'}';
+ }
+ }
+
+ /** A {@link FunctionChange} to add an implementation to a definition. */
+ final class AddImpl implements FunctionChange {
+ private final FunctionParam[] parameters;
+ private final FunctionImpl implementation;
+
+ AddImpl(FunctionParam[] parameters, FunctionImpl implementation) {
+ Preconditions.checkArgument(parameters != null, "Parameters cannot be
null");
+ this.parameters = Arrays.copyOf(parameters, parameters.length);
+ Preconditions.checkArgument(implementation != null, "Implementation
cannot be null");
+ this.implementation = implementation;
+ }
+
+ /**
+ * @return The parameters that identify the definition to update.
+ */
+ public FunctionParam[] parameters() {
+ return parameters.length == 0 ? EMPTY_PARAMS : Arrays.copyOf(parameters,
parameters.length);
+ }
+
+ /**
+ * @return The implementation to add.
+ */
+ public FunctionImpl implementation() {
+ return implementation;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof AddImpl)) {
+ return false;
+ }
+ AddImpl that = (AddImpl) obj;
+ return Arrays.equals(parameters, that.parameters)
+ && Objects.equals(implementation, that.implementation);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Arrays.hashCode(parameters);
+ result = 31 * result + Objects.hashCode(implementation);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "AddImpl{parameters="
+ + Arrays.toString(parameters)
+ + ", implementation="
+ + implementation
+ + '}';
+ }
+ }
+
+ /**
+ * A {@link FunctionChange} to replace an implementation (identified by
runtime) for a specific
+ * definition.
+ */
+ final class UpdateImpl implements FunctionChange {
+ private final FunctionParam[] parameters;
+ private final FunctionImpl.RuntimeType runtime;
+ private final FunctionImpl implementation;
+
+ UpdateImpl(
+ FunctionParam[] parameters, FunctionImpl.RuntimeType runtime,
FunctionImpl implementation) {
+ Preconditions.checkArgument(parameters != null, "Parameters cannot be
null");
+ this.parameters = Arrays.copyOf(parameters, parameters.length);
+ Preconditions.checkArgument(runtime != null, "Runtime cannot be null");
+ this.runtime = runtime;
+ Preconditions.checkArgument(implementation != null, "Implementation
cannot be null");
+ this.implementation = implementation;
+ Preconditions.checkArgument(
+ runtime == implementation.runtime(),
+ "Runtime of implementation must match the runtime being updated");
+ }
+
+ /**
+ * @return The parameters that identify the definition to update.
+ */
+ public FunctionParam[] parameters() {
+ return parameters.length == 0 ? EMPTY_PARAMS : Arrays.copyOf(parameters,
parameters.length);
+ }
+
+ /**
+ * @return The runtime that identifies the implementation to replace.
+ */
+ public FunctionImpl.RuntimeType runtime() {
+ return runtime;
+ }
+
+ /**
+ * @return The new implementation.
+ */
+ public FunctionImpl implementation() {
+ return implementation;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof UpdateImpl)) {
+ return false;
+ }
+ UpdateImpl that = (UpdateImpl) obj;
+ return Arrays.equals(parameters, that.parameters)
+ && runtime == that.runtime
+ && Objects.equals(implementation, that.implementation);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Arrays.hashCode(parameters);
+ result = 31 * result + Objects.hash(runtime, implementation);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "UpdateImpl{parameters="
+ + Arrays.toString(parameters)
+ + ", runtime="
+ + runtime
+ + ", implementation="
+ + implementation
+ + '}';
+ }
+ }
+
+ /** A {@link FunctionChange} to remove an implementation for a specific
runtime. */
+ final class RemoveImpl implements FunctionChange {
+ private final FunctionParam[] parameters;
+ private final FunctionImpl.RuntimeType runtime;
+
+ RemoveImpl(FunctionParam[] parameters, FunctionImpl.RuntimeType runtime) {
+ Preconditions.checkArgument(parameters != null, "Parameters cannot be
null");
+ this.parameters = Arrays.copyOf(parameters, parameters.length);
+ Preconditions.checkArgument(runtime != null, "Runtime cannot be null");
+ this.runtime = runtime;
+ }
+
+ /**
+ * @return The parameters that identify the definition to update.
+ */
+ public FunctionParam[] parameters() {
+ return parameters.length == 0 ? EMPTY_PARAMS : Arrays.copyOf(parameters,
parameters.length);
+ }
+
+ /**
+ * @return The runtime that identifies the implementation to remove.
+ */
+ public FunctionImpl.RuntimeType runtime() {
+ return runtime;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof RemoveImpl)) {
+ return false;
+ }
+ RemoveImpl that = (RemoveImpl) obj;
+ return Arrays.equals(parameters, that.parameters) && runtime ==
that.runtime;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Arrays.hashCode(parameters);
+ result = 31 * result + Objects.hashCode(runtime);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "RemoveImpl{parameters=" + Arrays.toString(parameters) + ",
runtime=" + runtime + '}';
+ }
+ }
+}
diff --git
a/api/src/main/java/org/apache/gravitino/function/FunctionColumn.java
b/api/src/main/java/org/apache/gravitino/function/FunctionColumn.java
new file mode 100644
index 0000000000..022bf78ff0
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/function/FunctionColumn.java
@@ -0,0 +1,110 @@
+/*
+ * 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.function;
+
+import com.google.common.base.Preconditions;
+import java.util.Objects;
+import javax.annotation.Nullable;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.annotation.Evolving;
+import org.apache.gravitino.rel.types.Type;
+
+/** Represents a return column of a table-valued function. */
+@Evolving
+public class FunctionColumn {
+ private final String name;
+ private final Type dataType;
+ private final String comment;
+
+ private FunctionColumn(String name, Type dataType, String comment) {
+ Preconditions.checkArgument(
+ StringUtils.isNotBlank(name), "Function column name cannot be null or
empty");
+ this.name = name;
+ Preconditions.checkArgument(dataType != null, "Function column type cannot
be null");
+ this.dataType = dataType;
+ this.comment = comment;
+ }
+
+ /**
+ * Create a {@link FunctionColumn} instance.
+ *
+ * @param name The column name.
+ * @param dataType The column type.
+ * @param comment The optional comment of the column.
+ * @return A {@link FunctionColumn} instance.
+ */
+ public static FunctionColumn of(String name, Type dataType, String comment) {
+ return new FunctionColumn(name, dataType, comment);
+ }
+
+ /**
+ * @return The column name.
+ */
+ public String name() {
+ return name;
+ }
+
+ /**
+ * @return The column type.
+ */
+ public Type dataType() {
+ return dataType;
+ }
+
+ /**
+ * @return The optional column comment, null if not provided.
+ */
+ @Nullable
+ public String comment() {
+ return comment;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof FunctionColumn)) {
+ return false;
+ }
+ FunctionColumn that = (FunctionColumn) obj;
+ return Objects.equals(name, that.name)
+ && Objects.equals(dataType, that.dataType)
+ && Objects.equals(comment, that.comment);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, dataType, comment);
+ }
+
+ @Override
+ public String toString() {
+ return "FunctionColumn{"
+ + "name='"
+ + name
+ + '\''
+ + ", dataType="
+ + dataType
+ + ", comment='"
+ + comment
+ + '\''
+ + '}';
+ }
+}
diff --git
a/api/src/main/java/org/apache/gravitino/function/FunctionDefinition.java
b/api/src/main/java/org/apache/gravitino/function/FunctionDefinition.java
new file mode 100644
index 0000000000..c7ea9562d4
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/function/FunctionDefinition.java
@@ -0,0 +1,40 @@
+/*
+ * 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.function;
+
+import org.apache.gravitino.annotation.Evolving;
+
+/**
+ * A function definition that pairs a specific parameter list with its
implementations. A single
+ * function can include multiple definitions (overloads), each with distinct
parameters and
+ * implementations.
+ */
+@Evolving
+public interface FunctionDefinition {
+
+ /**
+ * @return The parameters for this definition. Maybe an empty array for a
no-arg definition.
+ */
+ FunctionParam[] parameters();
+
+ /**
+ * @return The implementations associated with this definition.
+ */
+ FunctionImpl[] impls();
+}
diff --git
a/api/src/main/java/org/apache/gravitino/function/FunctionDefinitions.java
b/api/src/main/java/org/apache/gravitino/function/FunctionDefinitions.java
new file mode 100644
index 0000000000..af0c4472b5
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/function/FunctionDefinitions.java
@@ -0,0 +1,103 @@
+/*
+ * 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.function;
+
+import com.google.common.base.Preconditions;
+import java.util.Arrays;
+import org.apache.commons.lang3.ArrayUtils;
+
+/** Helper methods to create {@link FunctionDefinition} instances. */
+public final class FunctionDefinitions {
+
+ private FunctionDefinitions() {}
+
+ /**
+ * Create an array of {@link FunctionDefinition} instances.
+ *
+ * @param definitions The function definitions.
+ * @return An array of {@link FunctionDefinition} instances.
+ */
+ public static FunctionDefinition[] of(FunctionDefinition... definitions) {
+ Preconditions.checkArgument(
+ ArrayUtils.isNotEmpty(definitions), "Definitions cannot be null or
empty");
+ return Arrays.copyOf(definitions, definitions.length);
+ }
+
+ /**
+ * Create a {@link FunctionDefinition} instance.
+ *
+ * @param parameters The parameters for this definition, it may be null or
empty.
+ * @param impls The implementations for this definition, it must not be null
or empty.
+ * @return A {@link FunctionDefinition} instance.
+ */
+ public static FunctionDefinition of(FunctionParam[] parameters,
FunctionImpl[] impls) {
+ return new FunctionDefinitionImpl(parameters, impls);
+ }
+
+ private static final class FunctionDefinitionImpl implements
FunctionDefinition {
+ private final FunctionParam[] parameters;
+ private final FunctionImpl[] impls;
+
+ FunctionDefinitionImpl(FunctionParam[] parameters, FunctionImpl[] impls) {
+ this.parameters =
+ parameters == null ? new FunctionParam[0] :
Arrays.copyOf(parameters, parameters.length);
+ Preconditions.checkArgument(
+ impls != null && impls.length > 0, "Impls cannot be null or empty");
+ this.impls = Arrays.copyOf(impls, impls.length);
+ }
+
+ @Override
+ public FunctionParam[] parameters() {
+ return Arrays.copyOf(parameters, parameters.length);
+ }
+
+ @Override
+ public FunctionImpl[] impls() {
+ return Arrays.copyOf(impls, impls.length);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof FunctionDefinition)) {
+ return false;
+ }
+ FunctionDefinition that = (FunctionDefinition) obj;
+ return Arrays.equals(parameters, that.parameters()) &&
Arrays.equals(impls, that.impls());
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Arrays.hashCode(parameters);
+ result = 31 * result + Arrays.hashCode(impls);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "FunctionDefinition{parameters="
+ + Arrays.toString(parameters)
+ + ", impls="
+ + Arrays.toString(impls)
+ + '}';
+ }
+ }
+}
diff --git a/api/src/main/java/org/apache/gravitino/function/FunctionImpl.java
b/api/src/main/java/org/apache/gravitino/function/FunctionImpl.java
new file mode 100644
index 0000000000..58d283e342
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/function/FunctionImpl.java
@@ -0,0 +1,118 @@
+/*
+ * 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.function;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableMap;
+import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.annotation.Evolving;
+
+/**
+ * Base class of function implementations.
+ *
+ * <p>A function implementation must declare its language and optional
external resources. Concrete
+ * implementations are provided by {@link SQLImpl}, {@link JavaImpl}, and
{@link PythonImpl}.
+ */
+@Evolving
+public abstract class FunctionImpl {
+ /** Supported implementation languages. */
+ public enum Language {
+ /** SQL implementation. */
+ SQL,
+ /** Java implementation. */
+ JAVA,
+ /** Python implementation. */
+ PYTHON
+ }
+
+ /** Supported execution runtimes for function implementations. */
+ public enum RuntimeType {
+ /** Spark runtime. */
+ SPARK,
+ /** Trino runtime. */
+ TRINO;
+
+ /**
+ * Parse a runtime value from string.
+ *
+ * @param value Runtime name.
+ * @return Parsed runtime.
+ * @throws IllegalArgumentException If the runtime is not supported.
+ */
+ public static RuntimeType fromString(String value) {
+ Preconditions.checkArgument(StringUtils.isNotBlank(value), "Function
runtime must be set");
+ return RuntimeType.valueOf(value.trim().toUpperCase());
+ }
+ }
+
+ private final Language language;
+ private final RuntimeType runtime;
+ private final FunctionResources resources;
+ private final Map<String, String> properties;
+
+ /**
+ * Construct a {@link FunctionImpl}.
+ *
+ * @param language The language of the function implementation.
+ * @param runtime The runtime of the function implementation.
+ * @param resources The resources required by the function implementation.
+ * @param properties The properties of the function implementation.
+ */
+ protected FunctionImpl(
+ Language language,
+ RuntimeType runtime,
+ FunctionResources resources,
+ Map<String, String> properties) {
+ Preconditions.checkArgument(language != null, "Function implementation
language must be set");
+ this.language = language;
+ Preconditions.checkArgument(runtime != null, "Function runtime must be
set");
+ this.runtime = runtime;
+ this.resources = resources == null ? FunctionResources.empty() : resources;
+ this.properties = properties == null ? ImmutableMap.of() :
ImmutableMap.copyOf(properties);
+ }
+
+ /**
+ * @return The implementation language.
+ */
+ public Language language() {
+ return language;
+ }
+
+ /**
+ * @return The target runtime.
+ */
+ public RuntimeType runtime() {
+ return runtime;
+ }
+
+ /**
+ * @return The external resources required by this implementation.
+ */
+ public FunctionResources resources() {
+ return resources;
+ }
+
+ /**
+ * @return The additional properties of this implementation.
+ */
+ public Map<String, String> properties() {
+ return properties;
+ }
+}
diff --git a/api/src/main/java/org/apache/gravitino/function/FunctionImpls.java
b/api/src/main/java/org/apache/gravitino/function/FunctionImpls.java
new file mode 100644
index 0000000000..495a7ab099
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/function/FunctionImpls.java
@@ -0,0 +1,125 @@
+/*
+ * 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.function;
+
+import com.google.common.base.Preconditions;
+import java.util.Arrays;
+import java.util.Map;
+import org.apache.commons.lang3.ArrayUtils;
+
+/** Helper methods to create {@link FunctionImpl} instances. */
+public class FunctionImpls {
+
+ /**
+ * Copy an array of function implementations.
+ *
+ * @param impls The function implementations.
+ * @return A copy of the input array.
+ */
+ public static FunctionImpl[] of(FunctionImpl... impls) {
+ Preconditions.checkArgument(ArrayUtils.isNotEmpty(impls), "Impls cannot be
null or empty");
+ return Arrays.copyOf(impls, impls.length);
+ }
+
+ /**
+ * Create a SQL implementation.
+ *
+ * @param runtime Target runtime.
+ * @param sql SQL text body.
+ * @return A {@link SQLImpl} instance.
+ */
+ public static SQLImpl ofSql(FunctionImpl.RuntimeType runtime, String sql) {
+ return ofSql(runtime, sql, null, null);
+ }
+
+ /**
+ * Create a SQL implementation.
+ *
+ * @param runtime Target runtime.
+ * @param sql SQL text body.
+ * @param resources External resources required by the implementation.
+ * @param properties Additional implementation properties.
+ * @return A {@link SQLImpl} instance.
+ */
+ public static SQLImpl ofSql(
+ FunctionImpl.RuntimeType runtime,
+ String sql,
+ FunctionResources resources,
+ Map<String, String> properties) {
+ return new SQLImpl(runtime, sql, resources, properties);
+ }
+
+ /**
+ * Create a Java implementation.
+ *
+ * @param runtime Target runtime.
+ * @param className Fully qualified class name.
+ * @return A {@link JavaImpl} instance.
+ */
+ public static JavaImpl ofJava(FunctionImpl.RuntimeType runtime, String
className) {
+ return ofJava(runtime, className, null, null);
+ }
+
+ /**
+ * Create a Java implementation.
+ *
+ * @param runtime Target runtime.
+ * @param className Fully qualified class name.
+ * @param resources External resources required by the implementation.
+ * @param properties Additional implementation properties.
+ * @return A {@link JavaImpl} instance.
+ */
+ public static JavaImpl ofJava(
+ FunctionImpl.RuntimeType runtime,
+ String className,
+ FunctionResources resources,
+ Map<String, String> properties) {
+ return new JavaImpl(runtime, className, resources, properties);
+ }
+
+ /**
+ * Create a Python implementation.
+ *
+ * @param runtime Target runtime.
+ * @param handler Python handler entrypoint.
+ * @return A {@link PythonImpl} instance.
+ */
+ public static PythonImpl ofPython(FunctionImpl.RuntimeType runtime, String
handler) {
+ return ofPython(runtime, handler, null, null, null);
+ }
+
+ /**
+ * Create a Python implementation.
+ *
+ * @param runtime Target runtime.
+ * @param handler Python handler entrypoint.
+ * @param codeBlock Inline code block for the handler.
+ * @param resources External resources required by the implementation.
+ * @param properties Additional implementation properties.
+ * @return A {@link PythonImpl} instance.
+ */
+ public static PythonImpl ofPython(
+ FunctionImpl.RuntimeType runtime,
+ String handler,
+ String codeBlock,
+ FunctionResources resources,
+ Map<String, String> properties) {
+ return new PythonImpl(runtime, handler, codeBlock, resources, properties);
+ }
+}
diff --git a/api/src/main/java/org/apache/gravitino/function/FunctionParam.java
b/api/src/main/java/org/apache/gravitino/function/FunctionParam.java
new file mode 100644
index 0000000000..e0484451be
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/function/FunctionParam.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.function;
+
+import static org.apache.gravitino.rel.Column.DEFAULT_VALUE_NOT_SET;
+
+import javax.annotation.Nullable;
+import org.apache.gravitino.annotation.Evolving;
+import org.apache.gravitino.rel.expressions.Expression;
+import org.apache.gravitino.rel.types.Type;
+
+/** Represents a function parameter. */
+@Evolving
+public interface FunctionParam {
+
+ /**
+ * @return The name of the parameter.
+ */
+ String name();
+
+ /**
+ * @return The data type of the parameter.
+ */
+ Type dataType();
+
+ /**
+ * @return The optional comment of the parameter, null if not provided.
+ */
+ @Nullable
+ default String comment() {
+ return null;
+ }
+
+ /**
+ * @return The default value of the parameter if provided, otherwise {@link
+ * org.apache.gravitino.rel.Column#DEFAULT_VALUE_NOT_SET}.
+ */
+ default Expression defaultValue() {
+ return DEFAULT_VALUE_NOT_SET;
+ }
+}
diff --git
a/api/src/main/java/org/apache/gravitino/function/FunctionParams.java
b/api/src/main/java/org/apache/gravitino/function/FunctionParams.java
new file mode 100644
index 0000000000..743b99c88d
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/function/FunctionParams.java
@@ -0,0 +1,153 @@
+/*
+ * 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.function;
+
+import com.google.common.base.Preconditions;
+import java.util.Arrays;
+import java.util.Objects;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.rel.Column;
+import org.apache.gravitino.rel.expressions.Expression;
+import org.apache.gravitino.rel.types.Type;
+
+/** Helper methods to create {@link FunctionParam} instances. */
+public class FunctionParams {
+
+ private FunctionParams() {}
+
+ /**
+ * Create a copy of the given array of {@link FunctionParam} instances.
+ *
+ * @param params The array of parameters.
+ * @return A copy of the given array of {@link FunctionParam} instances.
+ */
+ public static FunctionParam[] of(FunctionParam... params) {
+ return params.length == 0 ? new FunctionParam[0] : Arrays.copyOf(params,
params.length);
+ }
+
+ /**
+ * Create a {@link FunctionParam} instance.
+ *
+ * @param name The parameter name.
+ * @param dataType The parameter type.
+ * @return A {@link FunctionParam} instance.
+ */
+ public static FunctionParam of(String name, Type dataType) {
+ return of(name, dataType, null, Column.DEFAULT_VALUE_NOT_SET);
+ }
+
+ /**
+ * Create a {@link FunctionParam} instance with an optional comment.
+ *
+ * @param name The parameter name.
+ * @param dataType The parameter type.
+ * @param comment The optional comment.
+ * @return A {@link FunctionParam} instance.
+ */
+ public static FunctionParam of(String name, Type dataType, String comment) {
+ return of(name, dataType, comment, Column.DEFAULT_VALUE_NOT_SET);
+ }
+
+ /**
+ * Create a {@link FunctionParam} instance with an optional comment and
default value.
+ *
+ * @param name The parameter name.
+ * @param dataType The parameter type.
+ * @param comment The optional comment.
+ * @param defaultValue The optional default value expression.
+ * @return A {@link FunctionParam} instance.
+ */
+ public static FunctionParam of(
+ String name, Type dataType, String comment, Expression defaultValue) {
+ return new FunctionParamImpl(name, dataType, comment, defaultValue);
+ }
+
+ private static final class FunctionParamImpl implements FunctionParam {
+ private final String name;
+ private final Type dataType;
+ private final String comment;
+ private final Expression defaultValue;
+
+ private FunctionParamImpl(String name, Type dataType, String comment,
Expression defaultValue) {
+ Preconditions.checkArgument(
+ StringUtils.isNotBlank(name), "Parameter name cannot be null or
empty");
+ Preconditions.checkArgument(dataType != null, "Parameter data type
cannot be null");
+ this.name = name;
+ this.dataType = dataType;
+ this.comment = comment;
+ this.defaultValue = defaultValue == null ? Column.DEFAULT_VALUE_NOT_SET
: defaultValue;
+ }
+
+ @Override
+ public String name() {
+ return name;
+ }
+
+ @Override
+ public Type dataType() {
+ return dataType;
+ }
+
+ @Override
+ public String comment() {
+ return comment;
+ }
+
+ @Override
+ public Expression defaultValue() {
+ return defaultValue;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof FunctionParam)) {
+ return false;
+ }
+ FunctionParam that = (FunctionParam) obj;
+ return Objects.equals(name, that.name())
+ && Objects.equals(dataType, that.dataType())
+ && Objects.equals(comment, that.comment())
+ && Objects.equals(defaultValue, that.defaultValue());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(name, dataType, comment, defaultValue);
+ }
+
+ @Override
+ public String toString() {
+ return "FunctionParam{"
+ + "name='"
+ + name
+ + '\''
+ + ", dataType="
+ + dataType
+ + ", comment='"
+ + comment
+ + '\''
+ + ", defaultValue="
+ + defaultValue
+ + '}';
+ }
+ }
+}
diff --git
a/api/src/main/java/org/apache/gravitino/function/FunctionResources.java
b/api/src/main/java/org/apache/gravitino/function/FunctionResources.java
new file mode 100644
index 0000000000..6fa49ec3da
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/function/FunctionResources.java
@@ -0,0 +1,112 @@
+/*
+ * 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.function;
+
+import java.util.Arrays;
+import java.util.Objects;
+import org.apache.gravitino.annotation.Evolving;
+
+/** Represents external resources that are required by a function
implementation. */
+@Evolving
+public class FunctionResources {
+ private static final String[] EMPTY = new String[0];
+
+ private final String[] jars;
+ private final String[] files;
+ private final String[] archives;
+
+ private FunctionResources(String[] jars, String[] files, String[] archives) {
+ this.jars = jars == null ? EMPTY : Arrays.copyOf(jars, jars.length);
+ this.files = files == null ? EMPTY : Arrays.copyOf(files, files.length);
+ this.archives = archives == null ? EMPTY : Arrays.copyOf(archives,
archives.length);
+ }
+
+ /**
+ * @return An empty {@link FunctionResources} instance.
+ */
+ public static FunctionResources empty() {
+ return new FunctionResources(EMPTY, EMPTY, EMPTY);
+ }
+
+ /**
+ * Create a {@link FunctionResources} instance.
+ *
+ * @param jars The jar resources.
+ * @param files The file resources.
+ * @param archives The archive resources.
+ * @return A {@link FunctionResources} instance.
+ */
+ public static FunctionResources of(String[] jars, String[] files, String[]
archives) {
+ return new FunctionResources(jars, files, archives);
+ }
+
+ /**
+ * @return The jar resources.
+ */
+ public String[] jars() {
+ return jars.length == 0 ? EMPTY : Arrays.copyOf(jars, jars.length);
+ }
+
+ /**
+ * @return The file resources.
+ */
+ public String[] files() {
+ return files.length == 0 ? EMPTY : Arrays.copyOf(files, files.length);
+ }
+
+ /**
+ * @return The archive resources.
+ */
+ public String[] archives() {
+ return archives.length == 0 ? EMPTY : Arrays.copyOf(archives,
archives.length);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof FunctionResources)) {
+ return false;
+ }
+ FunctionResources that = (FunctionResources) obj;
+ return Arrays.equals(jars, that.jars)
+ && Arrays.equals(files, that.files)
+ && Arrays.equals(archives, that.archives);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Objects.hash(Arrays.hashCode(jars), Arrays.hashCode(files));
+ result = 31 * result + Arrays.hashCode(archives);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "FunctionResources{"
+ + "jars="
+ + Arrays.toString(jars)
+ + ", files="
+ + Arrays.toString(files)
+ + ", archives="
+ + Arrays.toString(archives)
+ + '}';
+ }
+}
diff --git a/api/src/main/java/org/apache/gravitino/function/FunctionType.java
b/api/src/main/java/org/apache/gravitino/function/FunctionType.java
new file mode 100644
index 0000000000..43e7ea14da
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/function/FunctionType.java
@@ -0,0 +1,73 @@
+/*
+ * 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.function;
+
+import com.google.common.base.Preconditions;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.gravitino.annotation.Evolving;
+
+/** Function type supported by Gravitino. */
+@Evolving
+public enum FunctionType {
+ /** Scalar function. */
+ SCALAR,
+
+ /** Aggregate function. */
+ AGGREGATE,
+
+ /** Table-valued function. */
+ TABLE;
+
+ private static final Map<String, FunctionType> NAME_TO_TYPE;
+
+ static {
+ Map<String, FunctionType> map = new HashMap<>();
+ for (FunctionType value : values()) {
+ map.put(value.typeName().toLowerCase(Locale.ROOT), value);
+ }
+ map.put("agg", AGGREGATE);
+ NAME_TO_TYPE = Collections.unmodifiableMap(map);
+ }
+
+ /**
+ * Parse the function type from a string value.
+ *
+ * @param type the string to parse.
+ * @return the parsed {@link FunctionType}.
+ * @throws IllegalArgumentException if the value cannot be parsed.
+ */
+ public static FunctionType fromString(String type) {
+ Preconditions.checkArgument(
+ StringUtils.isNotBlank(type) &&
NAME_TO_TYPE.containsKey(type.toLowerCase(Locale.ROOT)),
+ "Invalid function type: " + type);
+ return NAME_TO_TYPE.get(type.toLowerCase(Locale.ROOT));
+ }
+
+ /**
+ * @return the canonical string representation used by APIs.
+ */
+ public String typeName() {
+ return name().toLowerCase(Locale.ROOT);
+ }
+}
diff --git a/api/src/main/java/org/apache/gravitino/function/JavaImpl.java
b/api/src/main/java/org/apache/gravitino/function/JavaImpl.java
new file mode 100644
index 0000000000..0a32467d40
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/function/JavaImpl.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.function;
+
+import com.google.common.base.Preconditions;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.commons.lang3.StringUtils;
+
+/** Java implementation with class name. */
+public class JavaImpl extends FunctionImpl {
+ private final String className;
+
+ JavaImpl(
+ RuntimeType runtime,
+ String className,
+ FunctionResources resources,
+ Map<String, String> properties) {
+ super(Language.JAVA, runtime, resources, properties);
+ Preconditions.checkArgument(
+ StringUtils.isNotBlank(className), "Java class name cannot be null or
empty");
+ this.className = className;
+ }
+
+ /**
+ * @return The fully qualified class name.
+ */
+ public String className() {
+ return className;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof JavaImpl)) {
+ return false;
+ }
+ JavaImpl that = (JavaImpl) obj;
+ return Objects.equals(language(), that.language())
+ && Objects.equals(runtime(), that.runtime())
+ && Objects.equals(resources(), that.resources())
+ && Objects.equals(properties(), that.properties())
+ && Objects.equals(className, that.className);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(language(), runtime(), resources(), properties(),
className);
+ }
+
+ @Override
+ public String toString() {
+ return "JavaImpl{"
+ + "language='"
+ + language()
+ + '\''
+ + ", runtime='"
+ + runtime()
+ + '\''
+ + ", className='"
+ + className
+ + '\''
+ + ", resources="
+ + resources()
+ + ", properties="
+ + properties()
+ + '}';
+ }
+}
diff --git a/api/src/main/java/org/apache/gravitino/function/PythonImpl.java
b/api/src/main/java/org/apache/gravitino/function/PythonImpl.java
new file mode 100644
index 0000000000..862bca1887
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/function/PythonImpl.java
@@ -0,0 +1,101 @@
+/*
+ * 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.function;
+
+import com.google.common.base.Preconditions;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.commons.lang3.StringUtils;
+
+/** Python implementation with handler and optional inline code. */
+public class PythonImpl extends FunctionImpl {
+ private final String handler;
+ private final String codeBlock;
+
+ PythonImpl(
+ RuntimeType runtime,
+ String handler,
+ String codeBlock,
+ FunctionResources resources,
+ Map<String, String> properties) {
+ super(Language.PYTHON, runtime, resources, properties);
+ Preconditions.checkArgument(
+ StringUtils.isNotBlank(handler), "Python handler cannot be null or
empty");
+ this.handler = handler;
+ this.codeBlock = codeBlock;
+ }
+
+ /**
+ * @return The handler entrypoint.
+ */
+ public String handler() {
+ return handler;
+ }
+
+ /**
+ * @return The Python UDF code block.
+ */
+ public String codeBlock() {
+ return codeBlock;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof PythonImpl)) {
+ return false;
+ }
+ PythonImpl that = (PythonImpl) obj;
+ return Objects.equals(language(), that.language())
+ && Objects.equals(runtime(), that.runtime())
+ && Objects.equals(resources(), that.resources())
+ && Objects.equals(properties(), that.properties())
+ && Objects.equals(handler, that.handler)
+ && Objects.equals(codeBlock, that.codeBlock);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(language(), runtime(), resources(), properties(),
handler, codeBlock);
+ }
+
+ @Override
+ public String toString() {
+ return "PythonImpl{"
+ + "language='"
+ + language()
+ + '\''
+ + ", runtime='"
+ + runtime()
+ + '\''
+ + ", handler='"
+ + handler
+ + '\''
+ + ", codeBlock='"
+ + codeBlock
+ + '\''
+ + ", resources="
+ + resources()
+ + ", properties="
+ + properties()
+ + '}';
+ }
+}
diff --git a/api/src/main/java/org/apache/gravitino/function/SQLImpl.java
b/api/src/main/java/org/apache/gravitino/function/SQLImpl.java
new file mode 100644
index 0000000000..a9070c34a7
--- /dev/null
+++ b/api/src/main/java/org/apache/gravitino/function/SQLImpl.java
@@ -0,0 +1,86 @@
+/*
+ * 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.function;
+
+import com.google.common.base.Preconditions;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.commons.lang3.StringUtils;
+
+/** SQL implementation with runtime and SQL body. */
+public class SQLImpl extends FunctionImpl {
+ private final String sql;
+
+ SQLImpl(
+ RuntimeType runtime,
+ String sql,
+ FunctionResources resources,
+ Map<String, String> properties) {
+ super(Language.SQL, runtime, resources, properties);
+ Preconditions.checkArgument(StringUtils.isNotBlank(sql), "SQL text cannot
be null or empty");
+ this.sql = sql;
+ }
+
+ /**
+ * @return The SQL that defines the function.
+ */
+ public String sql() {
+ return sql;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof SQLImpl)) {
+ return false;
+ }
+ SQLImpl that = (SQLImpl) obj;
+ return Objects.equals(language(), that.language())
+ && Objects.equals(runtime(), that.runtime())
+ && Objects.equals(resources(), that.resources())
+ && Objects.equals(properties(), that.properties())
+ && Objects.equals(sql, that.sql);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(language(), runtime(), resources(), properties(), sql);
+ }
+
+ @Override
+ public String toString() {
+ return "SQLImpl{"
+ + "language='"
+ + language()
+ + '\''
+ + ", runtime='"
+ + runtime()
+ + '\''
+ + ", sql='"
+ + sql
+ + '\''
+ + ", resources="
+ + resources()
+ + ", properties="
+ + properties()
+ + '}';
+ }
+}