使用GaussDB(DWS)数据库的PL/Java函数

201 阅读7分钟

PL/Java语言函数

使用GaussDB(DWS)数据库的PL/Java函数,用户可以使用自己喜欢的Java IDE编写Java方法,并将包含这些方法的jar文件安装到GaussDB(DWS)数据库中,然后使用该方法。GaussDB(DWS) PL/Java基于开源PL/Java 1.5.5开发,所使用的JRE版本为1.8.0_322。

使用限制

Java UDF可以实现一些java逻辑计算,强烈建议不要在Java UDF中封装业务

  • 强烈建议不要在Java函数中使用任何方式连接数据库,包括但不限于JDBC。
  • 暂不支持的数据类型:除表1内容之外的数据类型,包括自定义类型,复杂数据类型(Java Array类及派生类)。
  • 暂不支持UDAF,UDTF。

示例

使用PL/Java函数时,需要首先将Java方法的实现打包为jar包并且部署到数据库中,然后使用数据库管理员账号创建函数,考虑兼容性问题,请使用1.8.0_322版本的JRE进行编译。

  1. 编译jar包

    Java方法的实现和出包可以借助IDE来实现,以下是一个通过命令行来进行编译和出包的简单的示例,通过这个简单示例可以创建出一个包含单个方法的jar包文件。

    首先,编写一个Example.java文件,在此文件中实现子字符串大写转换的方法,本例中类名为Example,方法名为upperString,内容如下:

    | 1 2 3 4 5 6 7 | ``` public class Example { public static String upperString (String text, int beginIndex, int endIndex) { return text.substring(beginIndex, endIndex).toUpperCase(); } }

    | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    
    然后,创建manifest.txt清单文件,文件内容如下:
    
    | ```
    1 2 3 4 5 6
    ``` | ```
    Manifest-Version: 1.0 Main-Class: Example Specification-Title: "Example" Specification-Version: "1.0" Created-By: 1.6.0_35-b10-428-11M3811 Build-Date: 08/14/2018 10:09 AM 
    ``` |
    | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
    
    其中,Manifest-Version定义了manifest文件的版本,Main-Class定义了jar文件的入口类,Specification-Title和Specification-Version属于包的扩展属性,Specification-Title定义了扩展规范的标题,Specification-Version定义了扩展规范的版本,Created-By声明了该文件的生成者,Build-Date声明了该文件构建日期。
    
    最后,编译java文件并打包得到javaudf-example.jar
    
    | ```
    1 2
    ``` | ```
    javac Example.java jar cfm javaudf-example.jar manifest.txt Example.class 
    ``` |
    | ----------- | ---------------------------------------------------------------------------------- |
    
    ![]()
    
    jar包的命名规则应符合JDK命名要求,如果含有非法字符,在部署或者使用函数时将出错。
    
    
  2. 部署jar

    Jar包的部署需要omm用户在命令行中调用gs_om工具完成。以操作系统用户omm登录CN所在主机,执行source ${BIGDATA_HOME}/mppdb/.mppdbgs_profile命令启动环境变量。

    首先,修改jar包权限:

    | 1 | ``` chmod 644 javaudf-example.jar

    | --------- | -------------------------------------- |
    
    然后,将jar包部署到数据库中:
    
    | ```
    1
    ``` | ```
    gs_om -t javaUDF -m addjar -s javaudf-example.jar 
    ``` |
    | --------- | ---------------------------------------------------------- |
    
    gs_extend_library函数如何使用请参见[ 管理jar包和文件]()。函数中的AK/SK值,请用户根据实际获取值替换。region_name请用户根据实际所在的区域名称替换。
    
    
  3. 使用PL/Java 函数

    首先,使用拥有sysadmin权限的数据库用户(例如:omm)登录数据库并创建java_upperstring函数如下:

    | 1 2 3 4 | ``` CREATE FUNCTION java_upperstring(VARCHAR, INTEGER, INTEGER) RETURNS VARCHAR AS 'Example.upperString' LANGUAGE JAVA;

    | --------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
    
    ![]()
    
    -   函数java_upperstring中定义的数据类型为GaussDB(DWS)的数据类型。该数据类型需要和[步骤1]()中java定义的方法upperString中数据类型一一对应。GaussDB(DWS)与Java数据类型的对应关系,请参见[表1]()。
    -   AS子句用于指定该函数所调用的Java方法的类名和static方法名,格式为“类名.方法名”。该字段需要和[步骤1]()中java定义的类名和方法名一致。
    -   使用PL/Java函数时,LANGUAGE字段应指定为JAVA。
    -   CREATE FUNCTION更多说明,请参见[创建函数]()。
    
    然后,执行java_upperstring函数:
    
    | ```
    1
    ``` | ```
    SELECT java_upperstring('test', 0, 1); 
    ``` |
    | --------- | ----------------------------------------------- |
    
    得到预期结果为:
    
    | ```
    1 2 3 4
    ``` | ```
     java_upperstring ---------------------  T (1 row) 
    ``` |
    | --------------- | ----------------------------------------------------------- |
    
    
  4. 授权普通用户使用 PL/Java 函数

    创建普通用户,名称为udf_user。

    | 1 | ``` CREATE USER udf_user PASSWORD 'password';

    | --------- | -------------------------------------------------- |
    
    授权普通用户udf_user对java_upperstring函数的使用权限。注意,此处需要把函数所在模式和函数的使用权限同时赋予给用户,用户才可以使用此函数。
    
    | ```
    1 2
    ``` | ```
    GRANT ALL PRIVILEGES ON SCHEMA public TO udf_user; GRANT ALL PRIVILEGES ON FUNCTION java_upperstring(VARCHAR, INTEGER, INTEGER) TO udf_user; 
    ``` |
    | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
    
    以普通用户udf_user登录数据库。
    
    | ```
    1
    ``` | ```
    SET SESSION SESSION AUTHORIZATION udf_user PASSWORD 'password'; 
    ``` |
    | --------- | ------------------------------------------------------------------------ |
    
    执行java_upperstring函数:
    
    | ```
    1
    ``` | ```
    SELECT public.java_upperstring('test', 0, 1); 
    ``` |
    | --------- | ------------------------------------------------------ |
    
    得到预期结果为:
    
    | ```
    1 2 3 4
    ``` | ```
     java_upperstring ---------------------  T (1 row) 
    ``` |
    | --------------- | ----------------------------------------------------------- |
    
    
  5. 删除函数。

    如果不再使用该函数可以进行删除:

    | 1 | ``` DROP FUNCTION java_upperstring;

    | --------- | ---------------------------------------- |
    
    
  6. 卸载jar

    Jar包的卸载同样需要omm用户通过命令行调用gs_om工具完成:

    | 1 | ``` gs_om -t javaUDF -m rmjar -d javaudf-example.jar

    | --------- | --------------------------------------------------------- |
    

gs_om -t javaUDF工具

目前可以通过两种方式进行部署,一种是使用gs_om命令行工具,该工具可以部署任意格式文件,另一种是使用SQL函数gs_extend_library进行部署,该方式功能比较有限,建议线下用户使用gs_om命令行工具完成jar包或文件的部署。

  • 部署jar包或文件

    用户通过gs_om –t javaUDF –m addjar [-s LocalPath] [-d RemoteRelativePath] 命令实现jar包和其他配置文件的安装部署。

    -t javaUDF:操作名称。

    -m addjar:表明操作类型为新增文件。

    -s LocalPath:命令执行节点的绝对路径,可以是文件也可以是路径,不能为空。

    -d RemoteRelativePath:相对路径,可缺省,若LocalPath为目录,该操作可以把LocalPath目录下的所有文件以及子目录及文件拷贝到集群中所有服务器的GAUSSHOME/lib/java/[RemoteRelativePath]目录下;若LocalPath为文件,该操作可以把LocalPath文件拷贝到集群中所有服务器的GAUSSHOME/lib/java/[RemoteRelativePath]目录下;若LocalPath为文件,该操作可以把LocalPath文件拷贝到集群中所有服务器的GAUSSHOME/lib/java/[RemoteRelativePath]目录下。拷贝过程中如果存在冲突,提供是否覆盖选项y/n/a,分别表示是、否、全部替换,由用户根据实际需要自行选择策略。

  • 卸载jar包或文件

    用户通过gs_om –t javaUDF –m rmjar [-d RemoteRelativePath] 命令实现jar和其他配置文件的卸载。

    -t javaUDF:操作名称。

    -m rmjar:表明操作类型为删除文件。

    -d RemoteRelativePath:相对路径,可缺省,该操作可以删除集群中所有节点的指定目录$GAUSSHOME/lib/java/[RemoteRelativePath] 及其下层目录下的目录、jar包和文件。

  • 查看jar 包或文件

    线下用户通过gs_om -t javaUDF –m ls [-d RemoteRelativePath]命令查看已部署的jar和配置文件。并对文件一致性做校验。

    -t javaUDF:操作名称。

    -m ls:表明操作类型为查看文件。

    -d RemoteRelativePath:相对路径,可缺省,该操作可以查看集群中所有节点的指定目录$GAUSSHOME/lib /java/[RemoteRelativePath] 及其下层目录下的目录、jar包和文件(包括每个jar包或配置文件的相对路径),以执行节点为标准,对各节点中jar包及配置文件进行一致性检查。一致性检查会检查名称和MD5码,若有不一致给以警告。

SQL定义与使用

  • 管理jar包和文件

    拥有sysadmin权限的数据库用户可以使用gs_extend_library函数来部署、查看和删除数据库中的jar包,函数语法如下:

    | 1 | ``` SELECT gs_extend_library('[action]', '[operation]');

    | --------- | ------------------------------------------------------------- |
    
    ![]()
    
    -   **action**表明操作动作,值可以为:
    
        -   ls表示查看数据库中jar包,会对各节点的文件进行MD5值一致性检查。
        -   addjar表示将本地文件系统中的jar包部署到数据库中。
        -   rmjar表示将数据库中的jar包删除。
    
    -   **operation**表明操作字符串,格式为:
    
        file://[source_filepath] libraryname=[libraryname]
    
        -   source_filepath: 本地文件的绝对路径,仅支持jar文件。
        -   libraryname: 自定义库名,此自定义命名用于GaussDB(DWS)内对jar文件的调用。当action为addjar和rmjar时,该参数不可缺省,当action为ls时,该参数可以缺省。注意,自定义库名不允许含有/|;&$<>\'{}"()[]~*?!等字符。
    
    
  • 创建函数

    PL/Java函数通过CREATE FUNCTION语法创建,并且定义为LANGUAGE JAVA,且包含RETURNS和AS子句。

    • CREATE FUNCTION时指定所创建函数的名称,以及参数类型;

    • RETURNS子句用于指定该函数的返回类型;

    • AS子句与用于指定该函数所调用的Java方法的类名和static方法名,如果需要向Java方法传递NULL值作为入参,还需要指定该参数类型所对应的Java封装类名 (详见NULL值处理)。

    • 更多语法说明,请参见CREATE FUNCTION。

      | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | ``` CREATE [ OR REPLACE ] FUNCTION function_name ( [ { argname [ argmode ] argtype [ { DEFAULT | := | = } expression ]} [, …] ]) [ RETURNS rettype [ DETERMINISTIC ] ] LANGAUGE JAVA [ { IMMUTABLE | STATBLE | VOLATILE } | [ NOT ] LEAKPROOF | WINDOW | { CALLED ON NULL INPUT | RETURNS NULL ON NULL INPUT |STRICT } | {[ EXTERNAL ] SECURITY INVOKER | [ EXTERNAL ] SECURITY DEFINER | AUTHID DEFINER | AUTHID CURRENT_USER} | { FENCED } | COST execution_cost | ROWS result_rows | SET configuration_parameter { {TO |=} value | FROM CURRENT} ] […] { AS 'class_name.method_name' ( { argtype } [, …] ) }

      | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
      
      
  • 使用函数

    执行时,PL/Java会根据jar包名的字母序列,在所有部署的jar包中寻找函数指定的Java类,并调用首次找到的类中函数所指定的Java方法,并返回调用结果。

  • 删除函数

    PL/Java函数通过DROP FUNCTION语法删除函数。更多语法说明,请参见DROP FUNCTION。

    DROP FUNCTION [ IF EXISTS ] function_name [ ( [ {[ argmode ] [ argname ] argtype} [, ...] ] ) [ CASCADE | RESTRICT ] ];
    

    需要注意的是,如果所删除的函数为重载函数(详见重载函数),则删除时需要指明该函数的参数类型,如为非重载函数,则可直接指定函数名进行删除。

  • 函数授权

    非sysadmin户无法创建PL/Java函数,sysadmin用户可以赋予其他类型用户使用函数的权限。更多语法说明,请参见GRANT。

    GRANT { EXECUTE | ALL [ PRIVILEGES ] }
        ON { FUNCTION {function_name ( [ {[ argmode ] [ arg_name ] arg_type} [, ...] ] )} [, ...]
            | ALL FUNCTIONS IN SCHEMA schema_name [, ...] }
        TO { [ GROUP ] role_name | PUBLIC } [, ...]
        [ WITH GRANT OPTION ];
    

基本数据类型映射关系

GaussDB(DWS)Java
BOOLEANboolean
"char"byte
byteabyte[]
SMALLINTshort
INTEGERint
BIGINTlong
FLOAT4float
FLOAT8double
CHARjava.lang.String
VARCHARjava.lang.String
TEXTjava.lang.String
namejava.lang.String
DATEjava.sql.Timestamp
TIMEjava.sql.Time (stored value treated as local time)
TIMETZjava.sql.Time
TIMESTAMPjava.sql.Timestamp
TIMESTAMPTZjava.sql.Timestamp

数组类型处理

GaussDB(DWS)支持基础数组类型的转换,只需要在创建函数时在数据类型后追加 [] 即可,例如:

CREATE FUNCTION java_arrayLength(INTEGER[])
    RETURNS INTEGER
    AS 'Example.getArrayLength'
LANGUAGE JAVA;

Java代码类似于:

public class Example
{
    public static int getArrayLength(Integer[] intArray)
    {
        return intArray.length;
    }
}

那么下面的调用的语句后:

SELECT java_arrayLength(ARRAY[1, 2, 3]);

得到预期结果应该如下所示:

java_arrayLength
---------------------
3
(1 row)

NULL值处理

对于默认与Java的简单类型进行映射转换的那些GaussDB(DWS)数据类型,是无法处理NULL值的,如果希望在Java方法里能够获得并处理从GaussDB(DWS)中传入的NULL值,可以使用Java的封装类,并通过以下方式在AS子句中指定该Java封装类:

CREATE FUNCTION java_countnulls(INTEGER[])
    RETURNS INTEGER
    AS 'Example.countNulls(java.lang.Integer[])'
LANGUAGE JAVA;

Java代码类似于:

public class Example
{
    public static int countNulls(Integer[] intArray)
    {
        int nullCount = 0;
        for (int idx = 0; idx < intArray.length; ++idx)
        {
            if (intArray[idx] == null)
            nullCount++;
        }
        return nullCount;
    }
}

那么下面的调用的语句后:

SELECT java_countNulls(ARRAY[null, 1, null, 2, null]);

得到的预期结果应该如下所示:

java_countNulls
--------------------
3
(1 row)

重载函数

PL/Java支持重载函数,因此可以创建同名函数,或者调用Java代码中的重载方法。步骤如下:

  1. 创建重载函数

    例如,在Java中可以实现两个方法名相同,输入参数类型不同的方法dummy(int) 和dummy(String)

    public class Example
    {
        public static int dummy(int value)
        {
            return value*2;
        }
        public static String dummy(String value)
        {
            return value;
        }
    }
    

    并在GaussDB(DWS)中创建两个同名函数分别指定为上述两个方法:

    CREATE FUNCTION java_dummy(INTEGER)
        RETURNS INTEGER
        AS 'Example.dummy'
    LANGUAGE JAVA;
    
    CREATE FUNCTION java_dummy(VARCHAR)
        RETURNS VARCHAR
        AS 'Example.dummy'
    LANGUAGE JAVA;
    
  2. 调用重载函数

    在调用重载函数时,GaussDB(DWS)会根据输入的参数类型去调用匹配该类型的Java方法。因此上述两个函数的调用结果如下所示:

    SELECT java_dummy(5);
     java_dummy
    -----------------
                10
    (1 row)
    
    SELECT java_dummy('5');
     java_dummy
    ---------------
    5
    (1 row)
    

    需要注意的是,由于GaussDB(DWS)对数据类型存在隐式转换的情况,因此建议在调用重载函数时,指定输入参数的类型,例如:

    SELECT java_dummy(5::varchar);
     java_dummy
    ----------------
    5
    (1 row)
    

    此时会优先匹配所指定的参数类型,如果不存在指定参数类型的Java方法,则会对参数进行隐式转换匹配转换后的参数类型对应的Java方法。

    SELECT java_dummy(5::INTEGER);
     java_dummy
    -----------------
    10
    (1 row)
    
    DROP FUNCTION java_dummy(INTEGER);
    
    SELECT java_dummy(5::INTEGER);
     java_dummy
    ----------------
    5
    (1 row)
    

    隐式转换的数据类型包括:

    • 可以默认转换为INTEGER类型的包括:SMALLINT
    • 可以默认转换为BIGINT类型的包括:SMALLINT, INTEGER
    • 可以默认转换为BOOL类型的包括:TINYINT, SMALLINT, INTEGER, BIGINT
    • 可以默认转换为TEXT类型的包括:CHAR, NAME, BIGINT, INTEGER, SMALLINT, TINYINT, RAW, FLOAT4, FLOAT8, BPCHAR, VARCHAR, NVARCHAR2, DATE, TIMESTAMP, TIMESTAMPTZ, NUMERIC, SMALLDATETIME
    • 可以默认转换为VARCHAR类型的包括:TEXT, CHAR, BIGINT, INTEGER, SMALLINT, TINYINT, RAW, FLOAT4, FLOAT8, BPCHAR, DATE, NVARCHAR2, TIMESTAMP, NUMERIC, SMALLDATETIME
  3. 删除重载函数

    对于重载函数,删除时需要指定函数的参数类型,否则无法删除。

    DROP FUNCTION java_dummy(INTEGER);