Greenplum的PL/Java语言扩展

    上级主题:

    使用Greenplum数据库的PL/Java扩展,用户可以使用自己喜欢的Java IDE编写Java方法,并将包含这些方法的JAR文件安装到Greenplum数据库中。

    Greenplum数据库的PL/Java包基于开源PL/Java 1.4.0。 Greenplum数据库的PL/Java提供以下功能。

    • 能够使用Java 1.7或更高版本执行PL/Java函数。
    • 能够指定Java运行时间。
    • 在数据库中安装和维护Java代码的标准化实用程序(在SQL 2003提案之后设计)
    • 参数和结果的标准化映射。支持复杂类型集。
    • 使用Greenplum数据库内部SPI例程的嵌入式高性能JDBC驱动程序
    • 元数据支持JDBC驱动程序。 包括 DatabaseMetaData 和ResultSetMetaData。
    • 能够从查询中返回ResultSet,作为逐行构建ResultSet的替代方法。
    • 完全支持保留点和异常处理。
    • 能够使用IN,INPUT和OUT参数。
    • 两种独立的Greenplum数据库语言:
      • pljava, TRUSTED PL/Java language
      • pljavau, UNTRUSTED PL/Java language
    • 当一个事务或者保留点提交或者回滚时,事务和保留点监听器能够被编译执行。
    • 在所选平台上与GNU GCJ集成。

    SQL中的一个函数将在Java类中指定一个静态方法。为了使函数能够执行,所指定的类必须能够通过Greenplum数据库服务器上的pljava_classpath配置参数来指定类路径。 。PL/Java扩展添加了一组有助于安装和维护java类的函数。 类存储在普通的Java档案——JAR文件中。JAR文件可以选择性地包含部署描述符,该描述符又包含在部署或取消部署JAR时要执行的SQL命令。 这些功能是按照SQL 2003提出的标准进行设计的。

    PL/Java实现了传递参数和返回值的标准化方法。使用标准JDBC ResultSet类传递复杂类型和集合。

    PL/Java中包含JDBC驱动程序。 此驱动程序调用Greenplum数据库内部SPI例程。驱动程序是必不可少的,因为函数通常将调用数据库以获取数据。当PL/Java函数提取数据时, 它们必须使用与输入PL/Java执行上下文的主函数使用的相同的事务边界。

    PL/Java针对性能进行了优化。Java虚拟机在与后端相同的进程中执行,以最小化调用开销。 PL/Java的设计目的是为了使数据库本身能够实现Java的强大功能,以便数据库密集型业务逻辑可以尽可能靠近实际数据执行。

    当后端和Java VM之间的桥梁被调用时,将使用标准Java本机接口(JNI)。

    有关Greenplum数据库的PL/Java

    在标准PostgreSQL和Greenplum数据库中实现PL/Java有一些关键的区别。

    以下函数在Greenplum数据库中不被支持. 在分布式的Greenplum数据库环境中,类路径的处理方式与PostgreSQL环境下不同。

    • sqlj.install_jar
    • sqlj.replace_jar
    • sqlj.remove_jar
    • sqlj.get_classpath
    • sqlj.set_classpath

    Greenplum数据库使用 pljava_classpath 服务器配置参数代替 sqlj.set_classpath 函数。

    服务器配置参数

    以下服务器配置参数由PL/Java在Greenplum数据库中使用。这些参数取代了标准PostgreSQL PL/Java实现中使用的 pljava.* 参数:

    • pljava_classpath

      冒号(:) 分离的包含任何PL/Java函数中使用的Java类的jar文件列表。所有的jar文件必须安装在所有的Greenplum数据库主机的相同位置。 使用可信的PL/Java语言处理程序,jar文件路径必须相对于 $GPHOME/lib/postgresql/java/ 目录。 使用不受信任的语言处理程序(javaU语言标记), 路径可以相对于 $GPHOME/lib/postgresql/java/ 或使用绝对路径。

      服务器配置参数pljava_classpath_insecure 控制服务器配置参数pljava_classpath set by 是否可以由用户设置,无需Greenplum数据库超级用户权限。当启用 pljava_classpath_insecure 时,正在开发PL/Java函数的Greenplum数据库开发人员不必是数据库超级用户身份才能来更改 pljava_classpath.

      警告: 启用 pljava_classpath_insecure 通过为非管理员数据库用户提供能够运行未经授权的Java方法暴露了安全风险。

    • pljava_statement_cache_size

      为准备语句设置最近使用(MRU)缓存的大小(KB)。

    • pljava_release_lingering_savepoints

      如果为TRUE,在函数退出后,长期持续的保留点将会释放。 如果为 FALSE, 它们将被回滚。

    • pljava_vmoptions

      定义Greenplum数据库Java VM的启动选项。

    参阅 Greenplum数据库参考指南 有关Greenplum数据库服务器配置参数的信息。

    启用PL/Java并安装JAR文件

    执行以下步骤作为Greenplum数据库管理员 gpadmin。

    1. 通过在使用PL/Java的数据库中运行SQL脚本 $GPHOME/share/postgresql/pljava/install.sql 来启用 PL/Java。例如,此示例启用 PL/Java 在数据库 mytestdb:

      脚本 install.sql注册可信的和不可信的PL/Java语言.

    2. 将Java归档(JAR文件)复制到所有Greenplum数据库主机上的同一目录。 本示例使用Greenplum数据库 gpscp 程序将文件 myclasses.jar 复制到目录 $GPHOME/lib/postgresql/java/:

      1. =:/usr/local/greenplum-db/lib/postgresql/java/

      文件 gphosts_file 包含一个Greenplum数据库主机的列表。

    3. 设置pljava_classpath 服务器配置参数在postgresql.conf文件中。 对于此示例,参数值是冒号(:)分隔的JAR文件列表。 例如:

      1. $ gpconfig -c pljava_classpath
      2. -v \'examples.jar:myclasses.jar\'

      当用户使用 gppkg 实用程序安装PL/Java扩展包时, 将安装 examples.jar文件。

      注意: 如果将JAR文件安装在除 $GPHOME/lib/postgresql/java/则必须指定JAR文件的绝对路径。 所有的Greenplum数据库主机上的每个JAR文件必须位于相同的位置。 有关指定JAR文件位置的更多信息,参阅有关pljava_classpath 服务器配置参数的信息在Greenplum数据库 Reference Guide.

      1. $ gpstop -u
    4. (可选)Greenplum提供了一个包含可用于测试的示例PL/Java函数的 examples.sql 文件。 运行此文件中的命令来创建测试函数 (它使用 examples.jar中的Java类)。

      1. $ psql -f $GPHOME/share/postgresql/pljava/examples.sql

    编写PL/Java函数

    有关使用PL/Java编写函数的信息。

    SQL声明

    一个Java函数被声明为该类的一个类的名称和静态方法。该类将用于为该函数声明的模式定义的类路径进行解析。 如果没有为该模式定义类路径,则使用公共模式。如果没有找到类路径,则使用系统类加载器解析该类。

    可以声明以下函数来访问 java.lang.System 类上的静态方法getProperty:

    1. CREATE FUNCTION getsysprop(VARCHAR)
    2. RETURNS VARCHAR
    3. AS 'java.lang.System.getProperty'
    4. LANGUAGE java;

    运行以下命令返回user.home属性:

      类型映射

      标量类型以简单的方式映射。此表列出了当前的映射

      所有其他类型都映射到java.lang.String,并将使用为各自类型注册的标准 textin/textout 例程。

      NULL处理

      映射到java基元的标量类型不能作为NULL 值传递。要传递NULL 值, 这些类型可以有一个替代映射。用户可以通过在方法引用中明确的指定该映射来启用映射。

      1. CREATE FUNCTION trueIfEvenOrNull(integer)
      2. RETURNS bool
      3. AS 'foo.fee.Fum.trueIfEvenOrNull(java.lang.Integer)'
      4. LANGUAGE java;

      Java代码将类似于:

      1. package foo.fee;
      2. public class Fum
      3. {
      4. static boolean trueIfEvenOrNull(Integer value)
      5. {
      6. return (value == null)
      7. ? true
      8. : (value.intValue() % 2) == 0;
      9. }
      10. }

      以下两个语句都产生true:

      1. SELECT trueIfEvenOrNull(NULL);
      2. SELECT trueIfEvenOrNull(4);

      为了从Java方法返回 NULL 值, 可以使用与原始对象相对应的对象类型 (例如,返回 java.lang.Integer 而不是 int)。PL/Java解析机制找不到方法。由于Java对于具有相同名称的方法不能具有不同的返回类型,因此不会引入任何歧义。

      复杂类型将始终作为只读的 java.sql.ResultSet 传递,只有一行。ResultSet位于其行上,因此不应该调用 next() 。使用ResultSet的标准getter方法检索复杂类型的值。

      例如:

      1. CREATE TYPE complexTest
      2. AS(base integer, incbase integer, ctime timestamptz);
      3. CREATE FUNCTION useComplexTest(complexTest)
      4. RETURNS VARCHAR
      5. AS 'foo.fee.Fum.useComplexTest'
      6. IMMUTABLE LANGUAGE java;

      在java类Fum中,,我们添加以下静态方法:

      返回复杂类型

      Java没有规定任何创建ResultSet的方法。 因此,返回ResultSet不是一个选项。 SQL-2003草案建议将复杂的返回值作为IN / OUT参数处理。 PL/Java以这种方式实现了一个ResultSet。 如果用户声明一个返回复杂类型的函数,则需要使用带有最后一个参数类型为 java.sql.ResultSet的布尔返回类型的Java方法。 该参数将被初始化为一个空的可更新结果集,它只包含一行。

      假设已经创建了上一节中的 complexTest 类型。

      1. CREATE FUNCTION createComplexTest(int, int)
      2. RETURNS complexTest
      3. AS 'foo.fee.Fum.createComplexTest'
      4. IMMUTABLE LANGUAGE java;

      PL/Java方法解析现在将在Fum 类中找到以下方法:

      1. public static boolean complexReturn(int base, int increment,
      2. ResultSet receiver)
      3. throws SQLException
      4. {
      5. receiver.updateInt(1, base);
      6. receiver.updateInt(2, base + increment);
      7. receiver.updateTimestamp(3, new
      8. Timestamp(System.currentTimeMillis()));
      9. return true;
      10. }

      返回值表示接收方是否应被视为有效的元组(true)或NULL(false)。

      函数的返回集

      返回结果集时,不要在返回结果集之前构建结果集,因为构建大型结果集将消耗大量资源。 最好一次产生一行。 顺便提一句,那就是Greenplum数据库后端期望一个使用SETOF返回的函数。 那用户就可以返回SETOF的一个标量类型,如 int, float或varchar, 或者可以返回一个复合类型的SETOF。

      返回SETOF <标量类型>

      为了返回一组标量类型,用户需要创建一个实现java.util.Iterator 接口的Java方法。这是一个返回一个SETOF的varchar的方法的例子:

      1. CREATE FUNCTION javatest.getSystemProperties()
      2. RETURNS SETOF varchar
      3. AS 'foo.fee.Bar.getNames'
      4. IMMUTABLE LANGUAGE java;

      这个简单的Java方法返回一个迭代器:

      1. package foo.fee;
      2. import java.util.Iterator;
      3. public class Bar
      4. {
      5. public static Iterator getNames()
      6. {
      7. names.add("Lisa");
      8. names.add("Bob");
      9. names.add("Bill");
      10. names.add("Sally");
      11. return names.iterator();
      12. }
      13. }

      返回SETOF <复杂类型>

      返回SETOF <复杂类型>的方法必须使用接口 org.postgresql.pljava.ResultSetProvider 或 org.postgresql.pljava.ResultSetHandle。具有两个接口的原因是它们满足两种不同用例的最佳处理。前者适用于要动态创建要从SETOF函数返回的每一行的情况。 在用户要返回执行查询的结果的情况下,后者将生成。

      使用ResultSetProvider接口

      该接口有两种方法。布尔型 assignRowValues(java.sql.ResultSet tupleBuilder, int rowNumber) 和void close() 方法。 Greenplum数据库的查询执行器将重复调用assignRowValues直到它返回假或者直到执行器决定不需要更多行为止。然后它会调用close。

      用户可以通过以下方式使用此接口:

      1. CREATE FUNCTION javatest.listComplexTests(int, int)
      2. RETURNS SETOF complexTest
      3. AS 'foo.fee.Fum.listComplexTest'
      4. IMMUTABLE LANGUAGE java;

      该函数映射到一个返回实现 ResultSetProvider接口实例的静态java方法。

      1. public class Fum implements ResultSetProvider
      2. {
      3. private final int m_base;
      4. private final int m_increment;
      5. public Fum(int base, int increment)
      6. {
      7. m_base = base;
      8. m_increment = increment;
      9. }
      10. public boolean assignRowValues(ResultSet receiver, int
      11. currentRow)
      12. throws SQLException
      13. {
      14. // Stop when we reach 12 rows.
      15. //
      16. if(currentRow >= 12)
      17. return false;
      18. receiver.updateInt(1, m_base);
      19. receiver.updateInt(2, m_base + m_increment * currentRow);
      20. receiver.updateTimestamp(3, new
      21. Timestamp(System.currentTimeMillis()));
      22. return true;
      23. }
      24. public void close()
      25. {
      26. // Nothing needed in this example
      27. }
      28. public static ResultSetProvider listComplexTests(int base,
      29. int increment)
      30. throws SQLException
      31. return new Fum(base, increment);
      32. }
      33. }

      listComplextTests 方法被调用一次。 如果没有可用结果或 ResultSetProvider 实例,将返回 NULL。这里的Java类 Fum 实现了这个接口,所以它返回一个自己的实例。 然后将重复调用 assignRowValues 方法,直到返回false。 到那候,将会调用close。

      使用ResultSetHandle接口

      该接口类似于ResultSetProvider接口 因为它也有将在最后调用的 close() 方法, 但是,不是让evaluator调用一次构建一行的方法,而是返回一个ResultSet的方法。 查询evaluator将遍历该集合,并将RestulSet内容(一次一个元组)传递给调用者,直到对 next()的调用返回false或者evaluator决定不需要更多行。

      这是一个使用默认连接获取的语句执行查询的示例。适用于部署描述符的SQL看起来像这样:

      1. CREATE FUNCTION javatest.listSupers()
      2. RETURNS SETOF pg_user
      3. AS 'org.postgresql.pljava.example.Users.listSupers'
      4. LANGUAGE java;
      5. CREATE FUNCTION javatest.listNonSupers()
      6. RETURNS SETOF pg_user
      7. AS 'org.postgresql.pljava.example.Users.listNonSupers'
      8. LANGUAGE java;

      并且在Java包中org.postgresql.pljava.example加入了一个类Users:

      1. public class Users implements ResultSetHandle
      2. {
      3. private final String m_filter;
      4. private Statement m_statement;
      5. {
      6. m_filter = filter;
      7. }
      8. public ResultSet getResultSet()
      9. throws SQLException
      10. {
      11. m_statement =
      12. DriverManager.getConnection("jdbc:default:connection").cr
      13. eateStatement();
      14. return m_statement.executeQuery("SELECT * FROM pg_user
      15. WHERE " + m_filter);
      16. }
      17. public void close()
      18. throws SQLException
      19. {
      20. m_statement.close();
      21. }
      22. public static ResultSetHandle listSupers()
      23. {
      24. return new Users("usesuper = true");
      25. }
      26. public static ResultSetHandle listNonSupers()
      27. {
      28. return new Users("usesuper = false");
      29. }
      30. }

      PL/Java包含映射到PostgreSQL SPI函数的JDBC驱动程序。可以使用以下语句获取映射到当前事务的连接:

      1. Connection conn =
      2. DriverManager.getConnection("jdbc:default:connection");

      获取连接后,可以准备和执行类似于其他JDBC连接的语句。 这些是PL/Java JDBC驱动程序的限制:

      • 事务无法以任何方式进行管理。 因此,连接后用户不能用如下方法:
        • commit()
        • rollback()
        • setAutoCommit()
        • setTransactionIsolation()
      • 在保存点上也有一些限制。 保存点不能超过其设置的功能,并且必须由同一功能回滚或释放。
      • 从executeQuery() 返回的结果集始终为 FETCH_FORWARD和CONCUR_READ_ONLY.
      • 元数据仅在PL/Java 1.1或更高版本中可用。
      • CallableStatement (用于存储过程)没有实现。
      • Clob 和Blob 类型未完全实现, 需要更多工作。 byte[] 和 String 可分别用于 bytea 和text 。

      异常处理

      重点: 在函数返回之前,用户将无法继续执行后端函数,并且在后端生成异常时传播错误,除非用户使用了保存点 。当回滚保存点时,异常条件被重置,用户可以继续执行。

      保存点

      Greenplum数据库保存点使用java.sql.Connection接口公开。有两个限制。

      • 必须在设置的函数中回滚或释放保存点。
      • 保存点不能超过其设置的功能

      日志

      PL/Java使用标准的Java Logger。 因此,用户可以如下写:

      1. Logger.getAnonymousLogger().info( "Time is " + new
      2. Date(System.currentTimeMillis()));

      目前,记录器使用一个处理程序来映射Greenplum数据库配置设置的当前状态 log_min_messages 到有效的Logger级别,并使用Greenplum数据库后端功能输出所有消息 elog()。

      注解: The log_min_messages 该log_min_messages首次在执行会话中的PL/Java函数时,从数据库读取设置。在Java方面,在使用PL/Java的Greenplum数据库会话重新启动之前 ,特定会话中第一个PL/Java函数执行后,该设置不会更改。

      Logger级别和Greenplum数据库后端级别之间适用以下映射。

      表 2. PL/Java 日志 Levels
      java.util.logging.LevelGreenplum数据库 Level
      SEVERE ERRORERROR
      WARNINGWARNING
      CONFIGLOG
      INFOINFO
      FINEDEBUG1
      FINERDEBUG2
      FINESTDEBUG3

      只有数据库超级用户可以安装PL/Java。使用SECURITY DEFINER安装PL/Java实用程序函数,以便它们以授予函数创建者的访问权限执行。

      可信语言

      PL/Java 是一种可信 语言。 可信的PL/Java语言无法访问PostgreSQL定义可信语言所规定的文件系统。任何数据库用户都可以创建和访问受信任的语言的函数。

      PL/Java还为语言 javau安装语言处理程序。 此版本 v 不受信任 ,只有超级用户可以创建使用它的新函数。 任何用户都可以调用这些函数。

      一些PL/Java问题和解决方案

      当编写PL/Java时,将JVM映射到与Greenplum数据库后端代码相同的进程空间中,对于多个线程, 异常处理和内存管理,已经出现了一些问题。 这里是简要说明如何解决这些问题。

      多线程

      Java本身就是多线程的。Greenplum数据库后端不是。没有什么可以阻止开发人员在Java代码中使用多个Threads类。调用后端的终结器可能是从背景垃圾回收线程中产生的。 可能使用的几个第三方Java包使用多个线程。该模式在同一过程中如何与Greenplum数据库后端共存?

      解决方案

      解决方案很简单。PL/Java定义了一个特殊对象 Backend.THREADLOCK.。当初始化PL/Java时,后端立即抓取该对象监视器(即它将在​​此对象上同步)。当后端调用Java函数时,监视器将被释放,然后在调用返回时立即恢复。来自Java的所有呼叫到后端代码都在同一个锁上同步。 这确保一次只能有一个线程可以从Java调用后端,并且只能在后端正在等待返回Java函数调用的时候调用。

      异常处理

      Java经常使用try / catch / finally块。Greenplum数据库有时会使用一个异常机制来调用 excelongjmp来将控件转移到已知状态。 这样的跳转通常会有效地绕过JVM。

      解决方案

      后端现在允许使用宏 PG_TRY/PG_CATCH/PG_END_TRY 捕获错误, 并且在catch块中,可以使用ErrorData结构检查错误。 PL/Java实现了一个名为 org.postgresql.pljava.ServerException 的 java.sql.SQLException子类.,可以从该异常中检索和检查ErrorData。允许捕获处理程序发送回滚到保存点。 回滚成功后,执行可以继续。

      Java垃圾收集器与palloc()和堆栈分配

      原始类型始终按值传递。包括String type (这是必需的,因为Java使用双字节字符)。。复杂类型通常包含在Java对象中并通过引用传递。例如,Java对象可以包含指向palloced或stack分配的内存的指针, 并使用本机JNI调用来提取和操作数据。一旦调用结束,这些数据将变得陈旧。 进一步尝试访问这些数据最多只会产生非常不可预知的结果,但更有可能导致内存错误和崩溃。

      解决方案

      PL/Java包含的代码可以确保当MemoryContext或堆栈分配超出范围时,陈旧的指针被清除。 Java包装器对象可能会生效,但是使用它们的任何尝试将导致陈旧的本机处理异常。

      示例

      以下简单的Java示例创建一个包含单个方法并运行该方法的JAR文件。

      注意: 该示例需要Java SDK来编译Java文件。

      以下方法返回一个子字符串。

      在文本文件example.class中输入这些Java代码。

      manifest.txt文件的内容:

      1. Manifest-Version: 1.0
      2. Main-Class: Example
      3. Specification-Title: "Example"
      4. Specification-Version: "1.0"
      5. Created-By: 1.6.0_35-b10-428-11M3811
      6. Build-Date: 01/20/2013 10:09 AM

      编译java代码:

      1. javac *.java

      创建名为analytics.jar的JAR存档,其中包含JAR中的类文件和清单文件MANIFEST文件。

      1. jar cfm analytics.jar manifest.txt *.class

      将jar文件上传到Greenplum主机。

      运行gpscp 将jar文件复制到Greenplum Java目录的实用程序。 使用-f 选项指定包含主节点和分段主机列表的文件。

      1. gpscp -f gphosts_file analytics.jar
      2. =:/usr/local/greenplum-db/lib/postgresql/java/

      使用gpconfig 程序 设置Greenplum pljava_classpath 服务器配置参数。该参数列出已安装的jar文件。

      1. gpconfig -c pljava_classpath -v \'analytics.jar\'

      运行gpstop 实用程序 -u 选项重新加载配置文件。

      1. gpstop -u

      来自psql 命令行, 运行以下命令显示已安装的jar文件。

      1. show pljava_classpath

      以下SQL命令创建一个表并定义一个Java函数来测试jar文件中的方法:

      1. create table temp (a varchar) distributed randomly;
      2. insert into temp values ('my string');
      3. --Example function
      4. create or replace function java_substring(varchar, int, int)
      5. returns varchar as 'Example.substring' language java;
      6. --Example execution
      7. select java_substring(a, 1, 5) from temp;

      用户可以将内容放在一个文件 mysample.sql中,并从 psql 命令行运行该命令:

      1. > \i mysample.sql

      输出类似于:

      1. java_substring
      2. ----------------

      参考

      PL/Java 1.4.0 release - .