Java UDF 处理程序示例¶
本主题包括用 Java 编写的 UDF 处理程序代码的简单示例。
有关使用 Java 创建 UDF 处理程序的更多信息,请参阅 创建 Java UDF 处理程序。
本主题内容:
创建和调用简单的内联 Java UDF¶
以下语句创建和调用内联 Java UDF。此代码返回传递给它的 VARCHAR。
此函数使用可选 CALLED ON NULL INPUT
子句声明,以表示即使输入的值为 NULL,也会调用该函数。(不管有没有这个子句,这个函数都会返回 NULL,但是您可以用另一种方式修改代码以处理 NULL,例如,返回一个空字符串。)
创建 UDF:
create or replace function echo_varchar(x varchar)
returns varchar
language java
called on null input
handler='TestFunc.echoVarchar'
target_path='@~/testfunc.jar'
as
'class TestFunc {
public static String echoVarchar(String x) {
return x;
}
}';
调用 UDF:
SELECT echo_varchar('Hello');
+-----------------------+
| ECHO_VARCHAR('HELLO') |
|-----------------------|
| Hello |
+-----------------------+
将 NULL 传递给内联 Java UDF¶
这使用了上面定义的 echo_varchar()
UDF。此 SQL NULL
值被隐式转换为 Java null
,该 Java null
被返回并隐式转换回 SQL NULL
:
调用 UDF:
SELECT echo_varchar(NULL);
+--------------------+
| ECHO_VARCHAR(NULL) |
|--------------------|
| NULL |
+--------------------+
传递数组值¶
Java 方法可以通过以下两种方式之一接收 SQL 数组:
使用 Java 的数组功能。
使用 Java 的 *varargs*(可变的实参数量)功能。
在这两种情况下,SQL 代码都必须传递 ARRAY。
备注
请务必使用 Java 类型,该类型对 SQL 类型具有效映射。有关更多信息,请参阅 SQL-Java 数据类型映射。
通过 ARRAY 传递¶
将 Java 参数声明为数组。例如,以下方法中的第三个参数是字符串数组:
static int myMethod(int fixedArgument1, int fixedArgument2, String[] stringArray)
以下是完整示例:
创建并加载表:
CREATE TABLE string_array_table(id INTEGER, a ARRAY); INSERT INTO string_array_table (id, a) SELECT 1, ARRAY_CONSTRUCT('Hello'); INSERT INTO string_array_table (id, a) SELECT 2, ARRAY_CONSTRUCT('Hello', 'Jay'); INSERT INTO string_array_table (id, a) SELECT 3, ARRAY_CONSTRUCT('Hello', 'Jay', 'Smith');创建 UDF:
create or replace function concat_varchar_2(a ARRAY) returns varchar language java handler='TestFunc_2.concatVarchar2' target_path='@~/TestFunc_2.jar' as $$ class TestFunc_2 { public static String concatVarchar2(String[] strings) { return String.join(" ", strings); } } $$;调用 UDF:
SELECT concat_varchar_2(a) FROM string_array_table ORDER BY id; +---------------------+ | CONCAT_VARCHAR_2(A) | |---------------------| | Hello | | Hello Jay | | Hello Jay Smith | +---------------------+
通过可变实参传递¶
使用可变实参与使用数组非常相似。
在您的 Java 代码中,使用 Java 的可变实参声明风格:
static int myMethod(int fixedArgument1, int fixedArgument2, String ... stringArray)
以下是完整示例。此示例与前面的示例(对于数组)之间的唯一显著区别是该方法的参数声明。
创建并加载表:
CREATE TABLE string_array_table(id INTEGER, a ARRAY); INSERT INTO string_array_table (id, a) SELECT 1, ARRAY_CONSTRUCT('Hello'); INSERT INTO string_array_table (id, a) SELECT 2, ARRAY_CONSTRUCT('Hello', 'Jay'); INSERT INTO string_array_table (id, a) SELECT 3, ARRAY_CONSTRUCT('Hello', 'Jay', 'Smith');创建 UDF:
create or replace function concat_varchar(a ARRAY) returns varchar language java handler='TestFunc.concatVarchar' target_path='@~/TestFunc.jar' as $$ class TestFunc { public static String concatVarchar(String ... stringArray) { return String.join(" ", stringArray); } } $$;调用 UDF:
SELECT concat_varchar(a) FROM string_array_table ORDER BY id; +-------------------+ | CONCAT_VARCHAR(A) | |-------------------| | Hello | | Hello Jay | | Hello Jay Smith | +-------------------+
从内联 UDF 显式返回 NULL¶
以下代码显示如何显式返回 NULL 值。将 Java 值 null
转换为 SQL NULL
。
创建 UDF:
create or replace function return_a_null()
returns varchar
null
language java
handler='TemporaryTestLibrary.returnNull'
target_path='@~/TemporaryTestLibrary.jar'
as
$$
class TemporaryTestLibrary {
public static String returnNull() {
return null;
}
}
$$;
调用 UDF:
SELECT return_a_null();
+-----------------+
| RETURN_A_NULL() |
|-----------------|
| NULL |
+-----------------+
将 OBJECT 传递给内联 Java UDF¶
以下示例使用 SQL OBJECT 数据类型和相应的 Java 数据类型 (Map<String, String>
),并从 OBJECT 中提取一个值。此示例还表明您可以向 Java UDF 传递多个参数。
创建并加载一个表,它包含以下 OBJECT 类型的列:
CREATE TABLE objectives (o OBJECT);
INSERT INTO objectives SELECT PARSE_JSON('{"outer_key" : {"inner_key" : "inner_value"} }');
创建 UDF:
create or replace function extract_from_object(x OBJECT, key VARCHAR)
returns variant
language java
handler='VariantLibrary.extract'
target_path='@~/VariantLibrary.jar'
as
$$
import java.util.Map;
class VariantLibrary {
public static String extract(Map<String, String> m, String key) {
return m.get(key);
}
}
$$;
调用 UDF:
SELECT extract_from_object(o, 'outer_key'),
extract_from_object(o, 'outer_key')['inner_key'] FROM objectives;
+-------------------------------------+--------------------------------------------------+
| EXTRACT_FROM_OBJECT(O, 'OUTER_KEY') | EXTRACT_FROM_OBJECT(O, 'OUTER_KEY')['INNER_KEY'] |
|-------------------------------------+--------------------------------------------------|
| { | "inner_value" |
| "inner_key": "inner_value" | |
| } | |
+-------------------------------------+--------------------------------------------------+
将 GEOGRAPHY 值传递给内联 Java UDF¶
以下示例使用 SQL GEOGRAPHY 数据类型。
创建 UDF:
create or replace function geography_equals(x geography, y geography)
returns boolean
language java
packages=('com.snowflake:snowpark:1.2.0')
handler='TestGeography.compute'
as
$$
import com.snowflake.snowpark_java.types.Geography;
class TestGeography {
public static boolean compute(Geography geo1, Geography geo2) {
return geo1.equals(geo2);
}
}
$$;
您可以使用该 PACKAGES 子句来指定 Snowflake 系统包,例如 Snowpark 包。当您这样做时,您不需要同时将 Snowpark JAR 文件作为 IMPORTS 子句的值。有关 PACKAGES 的更多信息,请参阅 CREATE FUNCTION 可选参数。
创建数据并使用该数据调用 UDF:
create table geocache_table (id INTEGER, g1 GEOGRAPHY, g2 GEOGRAPHY);
insert into geocache_table (id, g1, g2) select
1, TO_GEOGRAPHY('POINT(-122.35 37.55)'), TO_GEOGRAPHY('POINT(-122.35 37.55)');
insert into geocache_table (id, g1, g2) select
2, TO_GEOGRAPHY('POINT(-122.35 37.55)'), TO_GEOGRAPHY('POINT(90.0 45.0)');
select id, g1, g2, geography_equals(g1, g2) as "EQUAL?"
from geocache_table
order by id;
输出类似于:
+----+--------------------------------------------------------+---------------------------------------------------------+--------+
| ID | G1 | G2 | EQUAL? |
+----+--------------------------------------------------------|---------------------------------------------------------+--------+
| 1 | { "coordinates": [ -122.35, 37.55 ], "type": "Point" } | { "coordinates": [ -122.35, 37.55 ], "type": "Point" } | TRUE |
| 2 | { "coordinates": [ -122.35, 37.55 ], "type": "Point" } | { "coordinates": [ 90.0, 45.0 ], "type": "Point" } | FALSE |
+----+--------------------------------------------------------+---------------------------------------------------------+--------+
将 VARIANT 值传递给内联 Java UDF¶
当您将该 SQL VARIANT 类型的值传递给 Java UDF 时,Snowflake 可以将该值转换为 Snowpark 包 中提供的 变体 类型。请注意,Snowpark 包 1.4.0 版本及更高版本支持该 Variant
。
Snowpark Variant
类型提供了在 Variant
和其他类型之间的转换值的方法。
要使用 Snowpark Variant
类型,请在创建 UDF 时使用 PACKAGES 子句指定 Snowpark 包。当您这样做时,您不需要同时将 Snowpark JAR 文件作为 IMPORTS 子句的值。有关 PACKAGES 更多信息,请参阅 CREATE FUNCTION 可选参数。
以下示例中的代码接收存储为 VARIANT 类型的 JSON 数据,然后使用 Snowpark 库中的 Variant
类型从 JSON 中检索 price
值。接收的 JSON 的结构类似于 JSON 中显示的 示例中使用的示例数据。
create or replace function retrieve_price(v variant)
returns integer
language java
packages=('com.snowflake:snowpark:1.4.0')
handler='VariantTest.retrievePrice'
as
$$
import java.util.Map;
import com.snowflake.snowpark_java.types.Variant;
public class VariantTest {
public static Integer retrievePrice(Variant v) throws Exception {
Map<String, Variant> saleMap = v.asMap();
int price = saleMap.get("vehicle").asMap().get("price").asInt();
return price;
}
}
$$;
使用 Java UDF 读取文件¶
您可以使用处理程序代码读取文件内容。例如,您可能需要读取文件,以使用处理程序处理非结构化数据。有关处理非结构化数据的更多信息以及示例代码,请参阅 使用 UDF 和过程处理程序处理非结构化数据。
该文件必须位于可供处理程序使用的 Snowflake 暂存区中。
要读取暂存文件的内容,处理程序可以:
读取其文件路径 在 IMPORTS 子句中静态指定 的文件。在运行时,代码会从 UDF 的主目录读取文件。
当您想在初始化期间访问文件时,这可能很有用。
通过调用
SnowflakeFile
类或InputStream
类的方法读取动态指定的文件。如果您需要访问调用方指定的文件,您可以这样做。有关更多信息,请参阅本主题中的以下内容:
SnowflakeFile
提供不适用于InputStream
的功能,如下表所述。类
输入
备注
SnowflakeFile
URL 格式:
带有作用域的 URL 可在函数的调用方不是其所有者时降低文件注入攻击的风险。
文件 URL 或 UDF 所有者有权访问的文件的字符串路径。
文件必须位于指定的内部暂存区或外部暂存区。
轻松访问其他文件属性,例如文件大小。
InputStream
URL 格式:
带有作用域的 URL 可在函数的调用方不是其所有者时降低文件注入攻击的风险。
该文件必须位于内部暂存区或外部暂存区。
先决条件¶
在 Java 处理程序代码可以在暂存区上读取文件之前,必须执行以下操作以使该文件可供代码使用:
创建一个可供处理程序使用的暂存区。
您可以使用外部暂存区或内部暂存区。如果您使用内部暂存区,则它必须是用户暂存区或命名暂存区;Snowflake 目前不支持将表暂存区用于 UDF 依赖关系。有关创建暂存区的更多信息,请参阅 CREATE STAGE。有关选择内部暂存区类型的更多信息,请参阅 为本地文件选择内部暂存区。
请记住,必须为执行从暂存区读取 SQL 操作的角色分配足够的暂存区权限。有关更多信息,请参阅 向用户定义函数授予权限。
将由代码读取的文件复制到暂存区上。
您可以使用 PUT 命令将文件从本地驱动器复制到暂存区。有关命令参考,请参阅 PUT。有关使用 PUT 暂存文件的信息,请参阅 从本地文件系统暂存数据文件。
读取 IMPORTS 中静态指定的文件¶
处理程序可以读取已在 CREATE FUNCTION 命令的 IMPORTS 子句中指定了暂存区路径的文件。
当您在 IMPORTS 子句中指定文件时,Snowflake 会将该文件从暂存区复制到 UDF 的*主目录*(也称为*导入目录*),该目录是 UDF 从中实际读取文件的目录。
由于导入的文件被复制到单个目录中,并且在该目录中必须具有唯一的名称,因此 IMPORTS 子句中的每个文件都必须具有不同的名称,即使文件在不同的暂存区或暂存区中的不同子目录中开始也是如此。
以下示例创建并调用读取文件的 Java UDF。
下面的 Java 源代码创建了一个名为 readFile
的 Java 方法。此 UDF 使用了这种方法。
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;
class TestReadRelativeFile {
public static String readFile(String fileName) throws IOException {
StringBuilder contentBuilder = new StringBuilder();
String importDirectory = System.getProperty("com.snowflake.import_directory");
String fPath = importDirectory + fileName;
Stream<String> stream = Files.lines(Paths.get(fPath), StandardCharsets.UTF_8);
stream.forEach(s -> contentBuilder.append(s).append("\n"));
return contentBuilder.toString();
}
}
以下 SQL 代码创建了 UDF。此代码假定 Java 源代码已经过编译并放入一个名为 TestReadRelativeFile.jar
的 JAR 文件中,然后导入 UDF。第二和第三个导入的文件 my_config_file_1.txt
和 my_config_file_2.txt
是 UDF 可以读取的配置文件。
CREATE FUNCTION file_reader(file_name VARCHAR)
RETURNS VARCHAR
LANGUAGE JAVA
IMPORTS = ('@my_stage/my_package/TestReadRelativeFile.jar',
'@my_stage/my_path/my_config_file_1.txt',
'@my_stage/my_path/my_config_file_2.txt')
HANDLER = 'my_package.TestReadRelativeFile.readFile';
此代码调用 UDF:
SELECT file_reader('my_config_file_1.txt') ...;
...
SELECT file_reader('my_config_file_2.txt') ...;
选择是访问压缩格式还是未压缩格式的文件¶
暂存区中的文件可以以压缩格式或未压缩格式存储。用户可以在将文件复制到暂存区之前对其进行压缩,也可以让 PUT 命令压缩文件。
当 Snowflake 将以 GZIP 格式压缩的文件从暂存区复制到 UDF 主目录时,Snowflake 可以按原样写入副本,或者 Snowflake 可以在写入文件之前解压缩内容。
如果暂存区中的文件已压缩,并且您希望也压缩 UDF 主目录中的副本,则在 IMPORTS 子句中指定文件名时,只需在 IMPORTS 子句中使用原始文件名(例如“MyData.txt.gz”)即可。例如:
... imports = ('@MyStage/MyData.txt.gz', ...)
如果暂存区中的文件采用 GZIP 压缩格式,但您希望对 UDF 主目录中的副本进行解压缩,则在 IMPORTS 子句中指定文件名时,请省略“.gz”扩展名。例如,如果暂存区包含“MyData.txt.gz”,但您希望 UDF 读取未压缩格式的文件,则在 IMPORTS 子句中指定“MyData.txt”。如果还没有名为“MyData.txt”的未压缩文件,则 Snowflake 会搜索“MyData.txt.gz”,并自动将解压缩副本写入 MyData 主目录中的“UDF.txt”。然后,UDF 可以打开并读取未压缩文件“MyData.txt”。
请注意,智能解压缩仅适用于 UDF 主目录中的副本;不会更改暂存区中的原始文件。
请遵循以下处理压缩文件的最佳实践:
遵循正确的文件命名规范。如果文件采用 GZIP 压缩格式,则在文件名末尾添加扩展名“.gz”。如果文件不是 GZIP 压缩格式,则文件名不要以 “.gz” 扩展名结尾。
避免创建名称仅扩展名“.gz”不同的文件。例如,不要在同一个暂存区和目录中同时创建“MyData.txt”和“MyData.txt.gz”,也不要尝试在同一个 CREATE FUNCTION 命令中同时导入“MyData.txt”和“MyData.txt.gz”。
不要压缩文件两次。例如,如果您手动压缩文件,然后在不使用 AUTO_COMPRESS = FALSE 的情况下 PUT 该文件,则该文件将再次被压缩。智能解压缩只会将其解压缩一次,因此数据(或 JAR)文件在 UDF 主目录中存储时仍会被压缩。
将来,Snowflake 可能会将智能解压缩扩展到 GZIP 以外的压缩算法。为防止将来出现兼容性问题,请将这些最佳实践应用于使用任何类型压缩的文件。
备注
JAR 文件也可以以压缩或未压缩的格式存储在暂存区内。在向 Java UDF 提供所有 JAR 压缩文件之前,Snowflake 会自动将其解压缩。
使用 SnowflakeFile
读取动态指定的文件¶
使用该 SnowflakeFile
类的方法,您可以使用 Java 处理程序代码从暂存区读取文件。该 SnowflakeFile
类包含在 Snowflake 上 Java UDF 处理程序可用的类路径中。
备注
为了使您的代码能够抵御文件注入攻击,在将文件的位置传递给 UDF 时,尤其是在函数的调用方不是其所有者时,请务必使用带有作用域的 URL。您可以使用内置函数 BUILD_SCOPED_FILE_URL 在 SQL 中创建带有作用域的 URL。有关 BUILD_SCOPED_FILE_URL 用途的更多信息,请参阅 非结构化数据简介。
要在本地开发 UDF 代码,请将包含 SnowflakeFile
的 Snowpark JAR 添加到代码的类路径中。有关 snowpark.jar
的信息,请参阅 为 Snowpark Java 设置开发环境。请注意,Snowpark 客户端应用程序不能使用此类。
使用 SnowflakeFile
时,不必在创建 UDF 时使用 IMPORTS 子句指定暂存文件或包含 SnowflakeFile
的 JAR,如同在 SQL 中使用 CREATE FUNCTION 语句。
以下示例中的代码使用 SnowflakeFile
从指定的暂存区位置读取文件。它使用来自 getInputStream
方法的 InputStream
,将文件的内容读入 String
变量。
CREATE OR REPLACE FUNCTION sum_total_sales(file string)
RETURNS INTEGER
LANGUAGE JAVA
HANDLER = 'SalesSum.sumTotalSales'
TARGET_PATH = '@jar_stage/sales_functions2.jar'
AS
$$
import java.io.InputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import com.snowflake.snowpark_java.types.SnowflakeFile;
public class SalesSum {
public static int sumTotalSales(String filePath) throws IOException {
int total = -1;
// Use a SnowflakeFile instance to read sales data from a stage.
SnowflakeFile file = SnowflakeFile.newInstance(filePath);
InputStream stream = file.getInputStream();
String contents = new String(stream.readAllBytes(), StandardCharsets.UTF_8);
// Omitted for brevity: code to retrieve sales data from JSON and assign it to the total variable.
return total;
}
}
$$;
调用 UDF,传递有作用域的 URL 中的文件位置,以降低文件注入攻击的可能性。
SELECT sum_total_sales(BUILD_SCOPED_FILE_URL('@sales_data_stage', '/car_sales.json'));
备注
UDF 所有者必须有权访问其位置不具有作用域 URLs 的任何文件。可以通过让处理程序代码使用新 SnowflakeFile.newInstance
参数的 boolean
值调用 requireScopedUrl
的方法来读取这些暂存文件。
以下示例在指定不需要作用域 SnowflakeFile.newInstance
时使用 URL。
String filename = "@my_stage/filename.txt";
String sfFile = SnowflakeFile.newInstance(filename, false);
使用 InputStream
读取动态指定的文件¶
通过将处理程序函数的实参设为 java.io.InputStream
变量,可以将文件内容直接读入 InputStream
。当函数的调用方想要将文件路径作为实参传递时,这可能很有用。
备注
为了使您的代码能够抵御文件注入攻击,在将文件的位置传递给 UDF 时,尤其是在函数的调用方不是其所有者时,请务必使用带有作用域的 URL。您可以使用内置函数 BUILD_SCOPED_FILE_URL 在 SQL 中创建带有作用域的 URL。有关 BUILD_SCOPED_FILE_URL 用途的更多信息,请参阅 非结构化数据简介。
以下示例中的代码具有一个处理程序函数 sumTotalSales
,该函数接受 InputStream
并返回 int
。在运行时,Snowflake 会自动将 file
变量路径处的文件内容分配给 stream
实参变量。
CREATE OR REPLACE FUNCTION sum_total_sales(file string)
RETURNS INTEGER
LANGUAGE JAVA
HANDLER = 'SalesSum.sumTotalSales'
TARGET_PATH = '@jar_stage/sales_functions2.jar'
AS
$$
import java.io.InputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class SalesSum {
public static int sumTotalSales(InputStream stream) throws IOException {
int total = -1;
String contents = new String(stream.readAllBytes(), StandardCharsets.UTF_8);
// Omitted for brevity: code to retrieve sales data from JSON and assign it to the total variable.
return total;
}
}
$$;
调用 UDF,传递有作用域的 URL 中的文件位置,以降低文件注入攻击的可能性。
SELECT sum_total_sales(BUILD_SCOPED_FILE_URL('@sales_data_stage', '/car_sales.json'));
创建和调用一个简单的暂存 Java UDF¶
以下语句创建了一个简单的 Java UDF。此示例通常遵循 整理文件 中描述的文件和目录结构。
创建和编译 Java 处理程序代码¶
在项目的根目录(此处为
my_udf
)中,创建一个用于保存源 .java 文件的src
子目录和一个用于保存生成的 .class 文件的classes
子目录。您的目录层次结构应类似于以下内容:
my_udf/ |-- classes/ |-- src/
在该
src
目录中,创建一个名为mypackage
的目录来保存其类在mypackage
包中的 .java 文件。在
mypackage
目录中,创建一个包含源代码的MyUDFHandler.java
文件。package mypackage; public class MyUDFHandler { public static int decrementValue(int i) { return i - 1; } public static void main(String[] argv) { System.out.println("This main() function won't be called."); } }
在项目根目录(此处为
my_udf
)中,使用javac
命令编译源代码。以下示例中的
javac
命令编译MyUDFHandler.java
,以在classes
目录中生成MyUDFHandler.class
文件。javac -d classes src/mypackage/MyUDFHandler.java
此示例包含以下实参:
-d classes
– 应将生成的类文件写入其中的目录。src/mypackage/MyUDFHandler.java
– .java 文件的路径,格式为:source_directory/package_directory/Java_file_name
。
将编译后的代码打包到 JAR 文件¶
或者,在项目根目录中创建一个名为
my_udf.manifest
的清单文件,其中包含以下属性:Manifest-Version: 1.0 Main-Class: mypackage.MyUDFHandler
从项目根目录中,运行
jar
命令以创建包含 .class 文件和清单的 JAR 文件。以下示例中的
jar
命令将mypackage
包文件夹中生成的MyUDFHandler.class
文件放入名为my_udf.jar
的 .jar 文件。该-C ./classes
标志指定 .class 文件的位置。jar cmf my_udf.manifest my_udf.jar -C ./classes mypackage/MyUDFHandler.class
此示例包含以下实参:
cmf
– 命令实参:c
用于创建 JAR 文件;m
用于指示使用指定的 .manifest 文件;以及f
用于为 JAR 文件指定名称。my_udf.manifest
– 清单文件。my_udf.jar
– 要创建的 JAR 文件的名称。-C ./classes
– 包含生成的 .class 文件的目录。mypackage/MyUDFHandler.class
– 要包含在 JAR 中的 .class 文件的包和名称。
使用编译后的处理程序将 JAR 文件上传到暂存区¶
在 Snowflake 中,创建一个名为
jar_stage
的暂存区来存储包含 UDF 处理程序的 JAR 文件。有关创建暂存区的更多信息,请参阅 CREATE STAGE。
使用
PUT
命令将 JAR 文件从本地文件系统复制到暂存区。
put file:///Users/Me/my_udf/my_udf.jar @jar_stage auto_compress = false overwrite = true ;您可以将
PUT
命令存储在脚本文件中,然后通过 SnowSQL 执行该文件。
snowsql
命令看起来类似于以下内容:snowsql -a <account_identifier> -w <warehouse> -d <database> -s <schema> -u <user> -f put_command.sql此示例假设用户的密码是在 SNOWSQL_PWD 环境变量中指定的。
使用编译后的代码作为处理程序创建 UDF¶
创建 UDF:
create function decrement_value(i numeric(9, 0))
returns numeric
language java
imports = ('@jar_stage/my_udf.jar')
handler = 'mypackage.MyUDFHandler.decrementValue'
;
调用 UDF:
select decrement_value(-15);
+----------------------+
| DECREMENT_VALUE(-15) |
|----------------------|
| -16 |
+----------------------+