PL / Java的安装
在Ubuntu系统上安装PL / Java很简单。 我将首先创建一个新模板template_java ,因此我仍然可以创建没有pl / java扩展名的数据库。
在命令行上,假设您是数据库超级用户,请输入
# apt-get install postgresql-9.1
# apt-get install postgresql-9.1-pljava-gcj$ createdb template_java
$ psql -d template_java -c 'update db_database set datistemplate='t' where datnam='template_java''
$ psql -d template_java -f /usr/share/postgresql-9.1-pljava/install.sql
局限性
预包装的Ubuntu软件包使用Gnu GCJ Java实现,而不是标准的OpenJDK或Sun实现。 GCJ将Java源文件编译为本机目标代码,而不是字节码。 PL / Java的最新版本是“受信任的” –可以依靠它们保留在其沙箱中。 除其他外,这意味着您无法访问服务器上的文件系统。
如果必须打破信任关系,则可以使用第二种语言“ javaU”。 不受信任的函数只能创建一个数据库超级用户。
更重要的是,此实现是单线程的。 如果您需要与其他服务器通信,请记住这一点至关重要。
需要考虑的事情是是否要使用GCJ编译自己的常用库,并将它们作为共享库加载到PostgreSQL服务器中。 共享库位于/usr/lib/postgresql/9.1/lib中 ,稍后我可能要说更多。
快速验证
通过编写快速测试功能,我们可以轻松地检查安装。 使用template_java创建临时数据库,然后输入以下SQL:
CREATE FUNCTION getsysprop(VARCHAR) RETURNS VARCHARAS 'java.lang.System.getProperty'LANGUAGE java;SELECT getsysprop('user.home');
结果,您应该得到“ / var / lib / postgresql”。
安装我们自己的方法
这是一个不错的开始,但是如果我们不能调用自己的方法,那么我们并不会真正受益。 幸运的是,添加我们自己的并不难。
一个简单的PL / Java过程是
package sandbox;public class PLJava {public static String hello(String name) {if (name == null) {return null;}return 'Hello, ' + name + '!';}
}
实现PL / Java过程的方法有两个简单的规则:
- 它们必须是公共静态的
- 如果任何参数为空 ,他们必须返回null
而已。
将Java类导入PostgreSQL服务器很简单。 假设包类在/tmp/sandbox.jar中,而我们启用Java的数据库是mydb 。 然后我们的命令是
--
-- load java library
--
-- parameters:
-- url_path - where the library is located
-- url_name - how the library is referred to later
-- deploy - should the deployment descriptor be used?
--
select sqlj.install_jar('file:///tmp/sandbox.jar', 'sandbox', true);--
-- set classpath to include new library.
--
-- parameters
-- schema - schema (or database) name
-- classpath - colon-separated list of url_names.
--
select sqlj.set_classpath('mydb', 'sandbox');-- -------------------
-- other procedures --
-- ---------------------
-- reload java library
--
select sqlj.replace_jar('file:///tmp/sandbox.jar', 'sandbox', true);--
-- remove java library
--
-- parameters:
-- url_name - how the library is referred to later
-- undeploy - should the deployment descriptor be used?
--
select sqlj.remove_jar('sandbox', true);--
-- list classpath
--
select sqlj.get_classpath('mydb');--
记住要设置类路径,这一点很重要。 库在卸载时会自动从类路径中删除,但安装后不会自动添加到类路径中。
我们还没有完全完成–我们仍然需要将新功能告诉系统。
--
-- create function
--
CREATE FUNCTION mydb.hello(varchar) RETURNS varcharAS 'sandbox.PLJava.hello'LANGUAGE java;--
-- drop this function
--
DROP FUNCTION mydb.hello(varchar);--
现在,我们可以以与其他任何存储过程相同的方式调用我们的java方法。
部署描述符
这里令人头疼–在安装库时必须显式创建函数,而在删除库时将其删除。 除了最简单的情况之外,这都是耗时且容易出错的。
幸运的是,有一个解决此问题的方法-部署描述符。 精确的格式由ISO / IEC 9075-13:2003定义,但是一个简单的示例就足够了。
SQLActions[] = {'BEGIN INSTALLCREATE FUNCTION javatest.hello(varchar)RETURNS varcharAS 'sandbox.PLJava.hello'LANGUAGE java;END INSTALL','BEGIN REMOVEDROP FUNCTION javatest.hello(varchar);END REMOVE'
}
您必须在jar的MANIFEST.MF文件中告知部署人员有关部署描述符的信息。 一个示例Maven插件是
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><version>2.3.1</version><configuration><archive><manifestSections><manifestSection><name>postgresql.ddr</name> <!-- filename --><manifestEntries><SQLJDeploymentDescriptor>TRUE</SQLJDeploymentDescriptor></manifestEntries></manifestSection></manifestSections></archive></configuration>
</plugin>
现在,数据库将在安装和删除我们的方法时知道它们。
内部查询
存储过程的“大赢家”之一是查询是在服务器本身上执行的,比通过编程接口运行查询要快得多。 我已经看到了一个过程,只需将查询到的循环从客户端移动到服务器,就可以通过Java花费30分钟以上将其缩短至不到一秒的时间。
内部连接的JDBC URL是“ jdbc:default:connection”。 您不能使用事务(因为您处于呼叫者的事务之内),但是只要您停留在单个呼叫中,就可以使用保存点。 我不知道您是否可以使用CallableStatements(还有其他存储过程)–您无法使用1.2版,但Ubuntu 11.10软件包使用1.4.2版。
标量值列表在Java世界中以迭代器的形式返回 ,在SQL世界中以SETOF的形式返回 。
public static Iterator<String> colors() {List<String> colors = Arrays.asList('red', 'green', 'blue');return colors.iterator();}
和
CREATE FUNCTION javatest.colors()RETURNS SETOF varcharAS 'sandbox.PLJava.colors'IMMUTABLE LANGUAGE java;
我添加了IMMUTABLE关键字,因为此函数将始终返回相同的值。 这允许数据库执行缓存和查询优化。
在开始之前,您不需要知道结果,甚至不需要知道结果的大小。 以下是被认为总是会终止的序列,但尚未得到证实。 (不幸的是,我忘记了序列的名称。)作为一个旁注,这不是一个完整的解决方案,因为它不检查溢出-正确的实现应对此进行检查或使用BigInteger。
public static Iterator seq(int start) {Iterator iter = null;try {iter = new SeqIterator(start);} catch (IllegalArgumentException e) {// should log error...}return iter;}public static class SeqIterator implements Iterator {private int next;private boolean done = false;public SeqIterator(int start) {if (start <= 0) {throw new IllegalArgumentException();}this.next = start;}@Overridepublic boolean hasNext() {return !done;}@Overridepublic Integer next() {int value = next;next = (next % 2 == 0) ? next / 2 : 3 * next + 1;done = (value == 1);return value;}@Overridepublic void remove() {throw new UnsupportedOperationException();}}
CREATE FUNCTION javatest.seq(int)RETURNS SETOF intAS 'sandbox.PLJava.seq'IMMUTABLE LANGUAGE java;
在所有条件都相同的情况下,最好根据需要创建每个结果。 如果查询具有LIMIT子句,通常可以减少内存占用并避免不必要的工作。
单元组
在ResultSet中返回一个元组。
public static boolean singleWord(ResultSet receiver) throws SQLException {receiver.updateString('English', 'hello');receiver.updateString('Spanish', 'hola');return true;}
和
CREATE TYPE word AS (English varchar,Spanish varchar);CREATE FUNCTION javatest.single_word()RETURNS wordAS 'sandbox.PLJava.singleWord'IMMUTABLE LANGUAGE java;
返回true表示有效结果,返回false表示无效结果。 可以用相同的方式将复杂类型传递给java方法-它是一个只读ResultSet ,只包含一行。
元组列表
返回复杂值列表需要一个实现两个接口之一的类。
org.postgresql.pljava.ResultSetProvider
当可以以编程方式或根据需要创建结果时,将使用ResultSetProvider 。
public static ResultSetProvider listWords() {return new WordProvider();}public static class WordProvider implements ResultSetProvider {private final Map<String,String> words = new HashMap<String,String>();private final Iterator<String> keys;public WordProvider() {words.put('one', 'uno');words.put('two', 'dos');words.put('three', 'tres');words.put('four', 'quatro');keys = words.keySet().iterator();}@Overridepublic boolean assignRowValues(ResultSet receiver, int currentRow)throws SQLException {if (!keys.hasNext()) {return false;}String key = keys.next();receiver.updateString('English', key);receiver.updateString('Spanish', words.get(key));return true;}@Overridepublic void close() throws SQLException {}}
和
CREATE FUNCTION javatest.list_words()RETURNS SETOF wordAS 'sandbox.PLJava.listWords'IMMUTABLE LANGUAGE java;
org.postgresql.pljava.ResultSetHandle
当方法使用内部查询时,通常使用ResultSetHandle 。
public static ResultSetHandle listUsers() {return new UsersHandle();}public static class UsersHandle implements ResultSetHandle {private Statement stmt;@Overridepublic ResultSet getResultSet() throws SQLException {stmt = DriverManager.getConnection('jdbc:default:connection').createStatement();return stmt.executeQuery('SELECT * FROM pg_user');}@Overridepublic void close() throws SQLException {stmt.close();} }
和
CREATE FUNCTION javatest.list_users()RETURNS SETOF pg_userAS 'sandbox.PLJava.listUsers'LANGUAGE java;
介面
我无法在标准maven存储库中获得pljava jar的最新副本。 我的解决方案是从PL / Java源tarball提取接口。 为了方便您在此处提供它们。
ResultSetProvider
// Copyright (c) 2004, 2005, 2006 TADA AB - Taby Sweden// Distributed under the terms shown in the file COPYRIGHT// found in the root folder of this project or at// http://eng.tada.se/osprojects/COPYRIGHT.htmlpackage org.postgresql.pljava;import java.sql.ResultSet;
import java.sql.SQLException;// An implementation of this interface is returned from functions and procedures// that are declared to return <code>SET OF</code> a complex type. //Functions that// return <code>SET OF</code> a simple type should simply return an// {@link java.util.Iterator Iterator}.// @author Thomas Hallgrenpublic interface ResultSetProvider
{// This method is called once for each row that should be returned from// a procedure that returns a set of rows. The receiver// is a {@link org.postgresql.pljava.jdbc.SingleRowWriter SingleRowWriter}// writer instance that is used for capturing the data for the row.// @param receiver Receiver of values for the given row.// @param currentRow Row number. First call will have row number 0.// @return <code>true</code> if a new row was provided, <code>false</code>// if not (end of data).// @throws SQLExceptionboolean assignRowValues(ResultSet receiver, int currentRow)throws SQLException;// Called after the last row has returned or when the query evaluator dec ides// that it does not need any more rows.//void close()throws SQLException;
}
ResultSetHandle
// Copyright (c) 2004, 2005, 2006 TADA AB - Taby Sweden
// Distributed under the terms shown in the file COPYRIGHT
// found in the root directory of this distribution or at
// http://eng.tada.se/osprojects/COPYRIGHT.htmlpackage org.postgresql.pljava;import java.sql.ResultSet;
import java.sql.SQLException;// An implementation of this interface is returned from functions and procedures// that are declared to return <code>SET OF</code> a complex type in the form// of a {@link java.sql.ResultSet}. The primary motivation for this interface is// that an implementation that returns a ResultSet must be able to close the// connection and statement when no more rows are requested.// @author Thomas Hallgrenpublic interface ResultSetHandle
{// An implementation of this method will probably execute a query// and return the result of that query.// @return The ResultSet that represents the rows to be returned.// @throws SQLExceptionResultSet getResultSet()throws SQLException;// Called after the last row has returned or when the query evaluator decides// that it does not need any more rows.void close()throws SQLException;
}
扳机
数据库触发器是存储过程,该过程在四个CRUD(创建-读取-更新-删除)操作中的三个操作之一期间自动运行。
- 插入 –为触发器提供了新值,并且可以修改值或直接禁止操作。
- 更新 -触发提供旧 的和新的值。 同样,它能够修改值或禁止操作。
- 删除 –为触发器提供旧值。 它不能修改该值,但可以禁止该操作。
触发器可以在操作之前或之后运行。 如果要修改值,可以在操作之前执行触发器。 如果要记录结果,可以在操作后执行它。
典型用法
插入和更新:数据验证
插入和更新操作的预触发可用于强制数据完整性和一致性。 在这种情况下,结果要么被接受,要么被禁止操作。
插入和更新:数据标准化和消毒
有时值可能具有多种表示形式,或者可能具有危险性。 预触发是清理数据的机会,例如整理XML或将<替换为<和>替换为>。
所有操作:审核日志记录
所有操作的后触发可用于强制执行审核日志记录。 应用程序可以记录自己的操作,但不能记录对数据库的直接访问。 这是解决此问题的方法。
可以为每一行或在完成整个语句后运行触发器。 更新触发器也可以是有条件的。
触发器可用于创建“可更新视图”。
PL / Java实现
可以在触发器中使用任何java方法,只要它是一个返回void且使用单个参数( TriggerData对象)的公共静态方法即可。 触发器可以称为“按行”或“按状态”。
“按行”的TriggerData包含一个单行只读ResultSet,作为更新和删除时的“旧”值,以及一个单行可更新ResultSet,作为插入和更新时的“新”值。 这可用于修改内容,记录操作等。
public class AuditTrigger {public static void auditFoobar(TriggerData td) throws SQLException {Connection conn = DriverManager.getConnection('jdbc:default:connection');PreparedStatement ps = conn.prepareStatement('insert into javatest.foobar_audit(what, whenn, data) values (?, ?, ?::xml)');if (td.isFiredByInsert()) {ps.setString(1, 'INSERT');} else if (td.isFiredByUpdate()) {ps.setString(1, 'UPDATE');} else if (td.isFiredByDelete()) {ps.setString(1, 'DELETE');}ps.setTimestamp(2, new Timestamp(System.currentTimeMillis()));ResultSet rs = td.getNew();if (rs != null) {ps.setString(3, toXml(rs));} else {ps.setNull(3, Types.VARCHAR);}ps.execute();ps.close();}// simple marshaler. We could use jaxb or similar librarystatic String toXml(ResultSet rs) throws SQLException {String foo = rs.getString(1);if (rs.wasNull()) {foo = '';}String bar = rs.getString(2);if (rs.wasNull()) {bar = '';}return String.format('<my-class><foo>%s</foo><bar>%s</bar></my-class>', foo, bar);}
}
CREATE TABLE javatest.foobar (foo varchar(10),bar varchar(10));CREATE TABLE javatest.foobar_audit (what varchar(10) not null,whenn timestamp not null,data xml);CREATE FUNCTION javatest.audit_foobar()RETURNS triggerAS 'sandbox.AuditTrigger.auditFoobar'LANGUAGE 'java';CREATE TRIGGER foobar_auditAFTER INSERT OR UPDATE OR DELETE ON javatest.foobarFOR EACH ROWEXECUTE PROCEDURE javatest.audit_foobar();
规则
PostgreSQL扩展是Rules 。 它们与触发器类似,但更加灵活。 一个重要的区别是,可以在SELECT语句上触发规则,而不仅仅是INSERT,UPDATE和DELETE。
规则与触发器不同,使用标准函数。
介面
和以前一样,我找不到最新版本的Maven存储库,为了方便起见,我还包含了这些文件。
触发数据
// Copyright (c) 2004, 2005, 2006 TADA AB - Taby Sweden// Distributed under the terms shown in the file COPYRIGHT// found in the root folder of this project or at// http://eng.tada.se/osprojects/COPYRIGHT.htmlpackage org.postgresql.pljava;import java.sql.ResultSet;
import java.sql.SQLException;// The SQL 2003 spec. does not stipulate a standard way of mapping// triggers to functions. The PLJava mapping use this interface. All// functions that are intended to be triggers must be public, static,// return void, and take a <code>TriggerData</code> as their argument.// // @author Thomas Hallgrenpublic interface TriggerData
{// Returns the ResultSet that represents the new row. This ResultSet wil// be null for delete triggers and for triggers that was fired for// statement. //The returned set will be updateable and positioned on a// valid row. When the trigger call returns, the trigger manager will se// the changes that has been made to this row and construct a new tuple// which will become the new or updated row.//// @return An updateable <code>ResultSet</code> containing one row or// null// @throws SQLException// if the contained native buffer has gone stale.//ResultSet getNew() throws SQLException;// Returns the ResultSet that represents the old row. This ResultSet wil// be null for insert triggers and for triggers that was fired for// statement.The returned set will be read-only and positioned on a// valid row.// // @return A read-only ResultSet containing one row or// null.// @throws SQLException// if the contained native buffer has gone stale.//ResultSet getOld() throws SQLException;//// Returns the arguments for this trigger (as declared in the <code>CREAT // E TRIGGER</code>// statement. If the trigger has no arguments, this method will return an// array with size 0.// // @throws SQLException// if the contained native buffer has gone stale.String[] getArguments() throws SQLException;
// Returns the name of the trigger (as declared in theCREATE TRIGGER// statement).//// @throws SQLException// if the contained native buffer has gone stale.//String getName() throws SQLException;
/**
//Returns the name of the table for which this trigger was created (as
//* declared in the <code>CREATE TRIGGER</code statement). * * @throws SQLException* if the contained native buffer has gone stale.
String getTableName() throws SQLException;
/// Returns the name of the schema of the table for which this trigger was created (as * declared in the <code>CREATE TRIGGER</code statement).
//@throws SQLException * if the contained native buffer has gone stale. */String getSchemaName() throws SQLException;
// Returns <code>true</code> if the trigger was fired after the statement or row action that it is associated with.
//@throws SQLException * if the contained native buffer has gone stale. boolean isFiredAfter() throws SQLException;
//Returns <code>true</code> if the trigger was fired before the * //statement or row action that it is associated with. * * @throws SQLException * if //the contained native buffer has gone stale. */
boolean isFiredBefore() throws SQLException;
//Returns <code>true</code> if this trigger is fired once for each row * //(as opposed to once for the entire statement). * * @throws SQLException * if the //contained native buffer has gone stale. */
boolean isFiredForEachRow() throws SQLException;
//Returns <code>true</code> if this trigger is fired once for the entire //statement (as opposed to once for each row). * * @throws SQLException * if the //contained native buffer has gone stale. */
boolean isFiredForStatement() throws SQLException;
//Returns <code>true</code> if this trigger was fired by a <code>DELETE</code>. * * @throws SQLException * if the contained native //buffer has gone stale. */
boolean isFiredByDelete() throws SQLException;
//Returns <code>true</code> if this trigger was fired by an //<code>INSERT</code>. * * @throws SQLException * if the contained native //buffer has gone stale. */
boolean isFiredByInsert() throws SQLException;
//Returns <code>true</code> if this trigger was fired by an //<code>UPDATE</code>. * * @throws SQLException * if the contained native //buffer has gone stale. */
boolean isFiredByUpdate() throws SQLException; // Returns the name of the table for which this trigger was created (as
// declared in the <code>CREATE TRIGGER</code statement). * * @throws //SQLException* if the contained native buffer has gone stale. */
String getTableName() throws SQLException;
// Returns the name of the schema of the table for which this trigger was created (as / declared in the <code>CREATE TRIGGER</code statement). * * @throws //SQLException * if the contained native buffer has gone stale. */
String getSchemaName() throws SQLException;
//Returns <code>true</code> if the trigger was fired after the statement // or row action that it is associated with. * * @throws SQLException * if the //contained native buffer has gone stale. */
boolean isFiredAfter() throws SQLException;
// Returns <code>true</code> if the trigger was fired before the * //statement or row action that it is associated with. * * @throws SQLException * if //the contained native buffer has gone stale. */
boolean isFiredBefore() throws SQLException;
// Returns <code>true</code> if this trigger is fired once for each row * //(as opposed to once for the entire statement). * * @throws SQLException * if the //contained native buffer has gone stale. */
boolean isFiredForEachRow() throws SQLException;
// Returns <code>true</code> if this trigger is fired once for the entire // statement (as opposed to once for each row). * * @throws SQLException * if the //contained native buffer has gone stale. */
boolean isFiredForStatement() throws SQLException;
// Returns <code>true</code> if this trigger was fired by a //<code>DELETE</code>. * * @throws SQLException * if the contained native //buffer has gone stale. */
boolean isFiredByDelete() throws SQLException;
// Returns <code>true</code> if this trigger was fired by an //<code>INSERT</code>. * * @throws SQLException * if the contained native //buffer has gone stale. */
boolean isFiredByInsert() throws SQLException;
// Returns <code>true</code> if this trigger was fired by an //<code>UPDATE</code>. * * @throws SQLException * if the contained native //buffer has gone stale. */
boolean isFiredByUpdate() throws SQLException; }/**
// Returns the name of the table for which this trigger was created (as
// declared in the <code>CREATE TRIGGER</code statement). * * @throws //SQLException* if the contained native buffer has gone stale. */
String getTableName() throws SQLException;
// Returns the name of the schema of the table for which this trigger was created (as // declared in the <code>CREATE TRIGGER</code statement). * * @throws //SQLException * if the contained native buffer has gone stale. */
String getSchemaName() throws SQLException;
/// Returns <code>true</code> if the trigger was fired after the //statement * or row action that it is associated with. * * @throws SQLException * if //the contained native buffer has gone stale. */
boolean isFiredAfter() throws SQLException;
// Returns <code>true</code> if the trigger was fired before the * //statement or row action that it is associated with. * * @throws SQLException * if //the contained native buffer has gone stale. */
boolean isFiredBefore() throws SQLException;
// Returns <code>true</code> if this trigger is fired once for each row * (//as opposed to once for the entire statement). * * @throws SQLException * if the //contained native buffer has gone stale. */
boolean isFiredForEachRow() throws SQLException;
// Returns <code>true</code> if this trigger is fired once for the entire // statement (as opposed to once for each row). * * @throws SQLException * if the //contained native buffer has gone stale. */
boolean isFiredForStatement() throws SQLException;
// Returns <code>true</code> if this trigger was fired by a //<code>DELETE</code>. * * @throws SQLException * if the contained native //buffer has gone stale. */
boolean isFiredByDelete() throws SQLException;
// Returns <code>true</code> if this trigger was fired by an //<code>INSERT</code>. * * @throws SQLException * if the contained native //buffer has gone stale. */
boolean isFiredByInsert() throws SQLException;
// Returns <code>true</code> if this trigger was fired by an //<code>UPDATE</code>. * * @throws SQLException * if the contained native //buffer has gone stale. */
boolean isFiredByUpdate() throws SQLException; }
TriggerException
// Copyright (c) 2004, 2005, 2006 TADA AB - Taby Sweden// Distributed under the terms shown in the file COPYRIGHT// found in the root folder of this project or at// http://eng.tada.se/osprojects/COPYRIGHT.htmlpackage org.postgresql.pljava;import java.sql.SQLException;// An exception specially suited to be thrown from within a method// designated to be a trigger function. The message generated by// this exception will contain information on what trigger and// what relation it was that caused the exception// // @author Thomas Hallgrenpublic class TriggerException extends SQLException
{private static final long serialVersionUID = 5543711707414329116L;private static boolean s_recursionLock = false;public static final String TRIGGER_ACTION_EXCEPTION = '09000';private static final String makeMessage(TriggerData td, String message){StringBuffer bld = new StringBuffer();bld.append('In Trigger ');if(!s_recursionLock){s_recursionLock = true;try{bld.append(td.getName());bld.append(' on relation ');bld.append(td.getTableName());}catch(SQLException e){bld.append('(exception while generating exception message)');}finally{s_recursionLock = false;}}if(message != null){bld.append(': ');bld.append(message);}return bld.toString();}// Create an exception based on the <code>TriggerData</code> that was// passed to the trigger method.// @param td The <code>TriggerData</code> that was passed to the trigger// method.public TriggerException(TriggerData td){super(makeMessage(td, null), TRIGGER_ACTION_EXCEPTION);}// Create an exception based on the <code>TriggerData</code> that was// passed to the trigger method and an additional message.// @param td The <code>TriggerData</code> that was passed to the trigger// method.// @param reason An additional message with info about the exception.public TriggerException(TriggerData td, String reason){super(makeMessage(td, reason), TRIGGER_ACTION_EXCEPTION);}
}
数据库中用户定义的类型存在争议。 它们不是标准的-在某些时候DBA必须创建它们-这就引入了可移植性问题。 标准工具对此一无所知。 您必须通过ResultSets和PreparedStatements中的“ struct”方法访问它们。
另一方面,还有很多东西只能通过byte []支持。 这样可以防止数据库函数和存储过程轻松地操作它们。
什么是好的用户定义类型? 它必须是原子的,并且必须可以通过存储过程来完成有意义的工作。 注意,数据库用户定义类型与Java类不是同一回事。 几乎所有Java类都应存储为标准元组,并且只有在有充分理由的情况下才应使用数据库UDT。
我喜欢的试金石是询问您是否曾经想过除了对象本身之外还缓存有关类型(而不是元组)的不变信息。 例如,X.509数字证书具有许多不可变字段,这些字段将是有效的搜索词,但是为每一行提取该信息非常昂贵。 (旁注:插入和更新记录时,可以使用触发器来提取信息。这可确保缓存的值始终准确。)
例子:
- 复数(存储过程:算术)
- 有理数(存储过程:算术)
- galois字段编号(存储过程:算术运算为固定值)
- 图像(存储过程:获取尺寸)
- PDF文档(存储过程:提取元素)
- 数字证书和私钥(存储过程:加密)
还应该解决的是正确的实施语言。 在PL / Java中创建原型很容易,但是您可以提出一个强有力的论点,即类型最终应实现为标准的PostgreSQL扩展,因为将来您有20岁的历史时,它们很可能会出现倾倒。 在某些重要方面,这只是问题的一小部分-问题不在于实际的存储和函数实现是用C还是Java编写的,而是它与系统其余部分的联系方式。
PL / Java实现
PL / Java用户定义的类型必须实现java.sql.SQLData接口,从字符串创建对象的静态方法以及从对象创建字符串的实例方法。 这些方法必须相辅相成–必须有可能在整个方向上沿任一方向运行值并将其取回原始值。
注意,双打通常是不可能的–这就是为什么您得到数字4.000000001或2.999999999的原因。 在这些情况下,您将尽力而为并警告用户。
在许多情况下,可以以二进制格式更有效地存储对象。 在PostgreSQL术语中,这些是TOAST类型。 这是通过实现两个与SQLInput和SQLOutput流一起使用的新方法来处理的。
接下来是有理类型的简单实现。
public class Rational implements SQLData {private long numerator;private long denominator;private String typeName;public static Rational parse(String input, String typeName)throws SQLException {Pattern pattern = Pattern.compile('(-?[0-9]+)( */ *(-?[0-9]+))?');Matcher matcher = pattern.matcher(input);if (!matcher.matches()) {throw new SQLException('Unable to parse rational from string \'' + input+ ''');}if (matcher.groupCount() == 3) {if (matcher.group(3) == null) {return new Rational(Long.parseLong(matcher.group(1)));}return new Rational(Long.parseLong(matcher.group(1)),Long.parseLong(matcher.group(3)));}throw new SQLException('invalid format: \'' + input+ ''');}public Rational(long numerator) throws SQLException {this(numerator, 1);}public Rational(long numerator, long denominator) throws SQLException {if (denominator == 0) {throw new SQLException('demominator must be non-zero');}// do a little bit of normalizationif (denominator < 0) {numerator = -numerator;denominator = -denominator;}this.numerator = numerator;this.denominator = denominator;}public Rational(int numerator, int denominator, String typeName)throws SQLException {this(numerator, denominator);this.typeName = typeName;}public String getSQLTypeName() {return typeName;}public void readSQL(SQLInput stream, String typeName) throws SQLException {this.numerator = stream.readLong();this.denominator = stream.readLong();this.typeName = typeName;}public void writeSQL(SQLOutput stream) throws SQLException {stream.writeLong(numerator);stream.writeLong(denominator);}public String toString() {String value = null;if (denominator == 1) {value = String.valueOf(numerator);} else {value = String.format('%d/%d', numerator, denominator);}return value;}/** Meaningful code that actually does something with this type was* intentionally left out.*/
}
和
/* The shell type */CREATE TYPE javatest.rational;/* The scalar input function */CREATE FUNCTION javatest.rational_in(cstring)RETURNS javatest.rationalAS 'UDT[sandbox.Rational] input'LANGUAGE java IMMUTABLE STRICT;/* The scalar output function */CREATE FUNCTION javatest.rational_out(javatest.rational)RETURNS cstringAS 'UDT[sandbox.Rational] output'LANGUAGE java IMMUTABLE STRICT;/* The scalar receive function */CREATE FUNCTION javatest.rational_recv(internal)RETURNS javatest.rationalAS 'UDT[sandbox.Rational] receive'LANGUAGE java IMMUTABLE STRICT;/* The scalar send function */CREATE FUNCTION javatest.rational_send(javatest.rational)RETURNS byteaAS 'UDT[sandbox.Rational] send'LANGUAGE java IMMUTABLE STRICT;CREATE TYPE javatest.rational (internallength = 16,input = javatest.rational_in,output = javatest.rational_out,receive = javatest.rational_recv,send = javatest.rational_send,alignment = int);
类型修饰符
PostgreSQL允许类型具有修饰符。 示例在“ varchar(200)”或“ numeric(8,2)”中。
PL / Java当前不支持此功能(通过'typmod_in'和'typmod_out'方法),但是我已经提交了一个请求。
演员表
如果您所能做的就是将值存储和检索为不透明对象,则自定义类型并不是特别有用。 为什么不使用bytea并完成它呢?
实际上,在许多UDT中,能够将UDT强制转换为其他类型是有意义的。 像复数或有理数之类的数字类型应该能够与标准整数和浮点数字类型相互转换(尽管有限制)。
这应该克制。
强制转换作为单参数静态方法实现。 在Java世界中,这些方法通常被命名为newInstance,因此我在这里也做同样的事情。
public static Rational newInstance(String input) throws SQLException {if (input == null) {return null;}return parse(input, 'javatest.rational');}public static Rational newInstance(int value) throws SQLException {return new Rational(value);}public static Rational newInstance(Integer value) throws SQLException {if (value == null) {return null;}return new Rational(value.intValue());}public static Rational newInstance(long value) throws SQLException {return new Rational(value);}public static Rational newInstance(Long value) throws SQLException {if (value == null) {return null;}return new Rational(value.longValue());}public static Double value(Rational value) throws SQLException {if (value == null) {return null;}return value.doubleValue();}
和
CREATE FUNCTION javatest.rational_string_as_rational(varchar) RETURNS javatest.rationalAS 'sandbox.Rational.newInstance'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_int_as_rational(int4) RETURNS javatest.rationalAS 'sandbox.Rational.newInstance'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_long_as_rational(int8) RETURNS javatest.rationalAS 'sandbox.Rational.newInstance'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_as_double(javatest.rational) RETURNS float8AS 'sandbox.Rational.value'LANGUAGE JAVA IMMUTABLE STRICT;CREATE CAST (varchar AS javatest.rational)WITH FUNCTION javatest.rational_string_as_rational(varchar)AS ASSIGNMENT;CREATE CAST (int4 AS javatest.rational)WITH FUNCTION javatest.rational_int_as_rational(int4)AS ASSIGNMENT;CREATE CAST (int8 AS javatest.rational)WITH FUNCTION javatest.rational_long_as_rational(int8)AS ASSIGNMENT;CREATE CAST (javatest.rational AS float8)WITH FUNCTION javatest.rational_as_double(javatest.rational)AS ASSIGNMENT;
(旁注: STRICT表示,如果任何参数为NULL,则该函数将返回NULL。这允许数据库进行一些优化。)
(旁注:如果java对象也是不可变的,我们可能只能使用IMMUTABLE标志。我们可能应该使Rational对象不可变,因为其他数字类型是不可变的。)
汇总功能
min()呢? 有理数是数字类型,因此它们不应该支持所有标准的聚合函数吗?
定义新的聚合函数很简单。 简单的集合函数只需要一个带有两个UDT值并返回一个的静态成员函数。 通过最大值,最小值,总和,乘积等可以很容易地看出这一点。更复杂的聚合需要包含状态信息的辅助UDT,采用一个状态UDT和一个UDT并返回状态UDT的静态方法,以及确定方法,采用最终状态UDT并产生结果。 用平均值很容易看到–您需要一个包含计数器和运行总和的状态类型。
以下是前一种类型的聚合函数的一些示例。
// compare two Rational objects. We use BigInteger to avoid overflow.public static int compare(Rational p, Rational q) {if (p == null) {return 1;} else if (q == null) {return -1;}BigInteger l = BigInteger.valueOf(p.getNumerator()).multiply(BigInteger.valueOf(q.getDenominator()));BigInteger r = BigInteger.valueOf(q.getNumerator()).multiply(BigInteger.valueOf(p.getDenominator()));return l.compareTo(r);}public static Rational min(Rational p, Rational q) {if ((p == null) || (q == null)) {return null;}return (p.compareTo(q) <= 0) ? p : q;}public static Rational max(Rational p, Rational q) {if ((p == null) || (q == null)) {return null;}return (q.compareTo(p) < 0) ? p : q;}public static Rational add(Rational p, Rational q) throws SQLException {if ((p == null) || (q == null)) {return null;}BigInteger n = BigInteger.valueOf(p.getNumerator()).multiply(BigInteger.valueOf(q.getDenominator())).add(BigInteger.valueOf(q.getNumerator()).multiply(BigInteger.valueOf(p.getDenominator())));BigInteger d = BigInteger.valueOf(p.getDenominator()).multiply(BigInteger.valueOf(q.getDenominator()));BigInteger gcd = n.gcd(d);n = n.divide(gcd);d = d.divide(gcd);return new Rational(n.longValue(), d.longValue());}
和
CREATE FUNCTION javatest.min(javatest.rational, javatest.rational) RETURNS javatest.rationalAS 'sandbox.Rational.min'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.max(javatest.rational, javatest.rational) RETURNS javatest.rationalAS 'sandbox.Rational.max'LANGUAGE JAVA IMMUTABLE STRICT;CREATE AGGREGATE min(javatest.rational) (sfunc = javatest.min,stype = javatest.rational);CREATE AGGREGATE max(javatest.rational) (sfunc = javatest.max,stype = javatest.rational);CREATE AGGREGATE sum(javatest.rational) (sfunc = javatest.add,stype = javatest.rational);
与Hibernate集成
可以链接PL / Java用户定义类型和Hibernate用户定义类型。 警告:休眠代码是特定于数据库的。
这是休眠的用户定义类型。 PostgreSQL 9.1不支持STRUCT类型,而是使用字符串。 我们不必使用PL / Java用户定义的数据类型来执行封送处理,但它可以确保一致性。 DbRationalType是上面的Rational类。 可以在两个地方使用相同的类,但是会将对Hibernate接口的依赖性引入PL / Java类。 如果您从Hibernate源代码中提取单个接口,这可能是可以接受的。
public class Rational implements UserType, Serializable {private final int[] sqlTypesSupported = new int[] { Types.OTHER };private long numerator;private long denominator;public Rational() {numerator = 0;denominator = 1;}public Rational(long numerator, long denominator) {this.numerator = numerator;this.denominator = denominator;}public long getNumerator() {return numerator;}public long getDenominator() {return denominator;}@Overridepublic Object assemble(Serializable cached, Object owner)throws HibernateException {if (!(cached instanceof Rational)) {throw new HibernateException('invalid argument');}Rational r = (Rational) cached;return new Rational(r.getNumerator(), r.getDenominator());}@Overridepublic Serializable disassemble(Object value) throws HibernateException {if (!(value instanceof Rational)) {throw new HibernateException('invalid argument');}return (Rational) value;}@Overridepublic Object deepCopy(Object value) throws HibernateException {if (value == null) {return null}if (!(value instanceof Rational)) {throw new HibernateException('invalid argument');}Rational v = (Rational) value;return new Rational(v.getNumerator(), v.getDenominator());}@Overridepublic boolean isMutable() {return true;}//// important: PGobject is postgresql-specific// @Overridepublic Object nullSafeGet(ResultSet rs, String[] names, Object owners)throws HibernateException, SQLException {PGobject pgo = (PGobject) rs.getObject(names[0]);if (rs.wasNull()) {return null;}TheDbRationalType r = TheDbRationalType.parse(pgo.getValue(), 'rational');return new Rational(r.getNumerator(), r.getDenominator());}//// important: using Types.OTHER may be postgresql-specific// @Overridepublic void nullSafeSet(PreparedStatement ps, Object value, int index)throws HibernateException, SQLException {if (value == null) {ps.setNull(index, Types.OTHER);} else if (!(value instanceof Rational)) {throw new HibernateException('invalid argument');} else {Rational t = (Rational) value;ps.setObject(index,new TheDbRationalType(t.getNumerator(), t.getDenominator()), Types.OTHER);}}@Overridepublic Object replace(Object original, Object target, Object owner)throws HibernateException {if (!(original instanceof Rational)|| !(target instanceof Rational)) {throw new HibernateException('invalid argument');}Rational r = (Rational) original;return new Rational(r.getNumerator(), r.getDenominator());}@Overridepublic Class returnedClass() {return Rational.class;}@Overridepublic int[] sqlTypes() {return sqlTypesSupported;}@Overridepublic String toString() {String value = '';if (denominator == 1) {value = String.valueOf(numerator);} else {value = String.format('%d/%d', numerator, denominator);}return value;}// for UserType@Overridepublic int hashCode(Object value) {Rational r = (Rational) value;return (int) (31 * r.getNumerator() + r.getDenominator());}@Overridepublic int hashCode() {return hashCode(this);}// for UserType@Overridepublic boolean equals(Object left, Object right) {if (left == right) {return true;}if ((left == null) || (right == null)) {return false;}if (!(left instanceof Rational) || !(right instanceof Rational)) {return false;}Rational l = (Rational) left;Rational r = (Rational) right;return (l.getNumerator() == r.getNumerator())&& (l.getDenominator() == r.getDenominator());}@Overridepublic boolean equals(Object value) {return equals(this, value);}
}
CustomTypes.hbm.xml
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC'-//Hibernate/Hibernate Mapping DTD 3.0//EN''http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd'><hibernate-mapping><typedef name='javatest.rational' class='sandbox.RationalType'/></hibernate-mapping>
TestTable.hbm.xml
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-mapping PUBLIC'-//Hibernate/Hibernate Mapping DTD 3.0//EN''http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd'><hibernate-mapping><class name='sandbox.TestTable' table='test_table'><id name='id'/><property name='value' type='javatest.rational' /></class></hibernate-mapping>
经营者
运算符是普通的PL / Java方法,也通过CREATE OPERATOR语句标记为运算符。
支持有理数的基本算法为
public static Rational negate(Rational p) throws SQLException {if (p == null) {return null;}return new Rational(-p.getNumerator(), p.getDenominator());}public static Rational add(Rational p, Rational q) throws SQLException {if ((p == null) || (q == null)) {return null;}BigInteger n = BigInteger.valueOf(p.getNumerator()).multiply(BigInteger.valueOf(q.getDenominator())).add(BigInteger.valueOf(q.getNumerator()).multiply(BigInteger.valueOf(p.getDenominator())));BigInteger d = BigInteger.valueOf(p.getDenominator()).multiply(BigInteger.valueOf(q.getDenominator()));BigInteger gcd = n.gcd(d);n = n.divide(gcd);d = d.divide(gcd);return new Rational(n.longValue(), d.longValue());}public static Rational subtract(Rational p, Rational q) throws SQLException {if ((p == null) || (q == null)) {return null;}BigInteger n = BigInteger.valueOf(p.getNumerator()).multiply(BigInteger.valueOf(q.getDenominator())).subtract(BigInteger.valueOf(q.getNumerator()).multiply(BigInteger.valueOf(p.getDenominator())));BigInteger d = BigInteger.valueOf(p.getDenominator()).multiply(BigInteger.valueOf(q.getDenominator()));BigInteger gcd = n.gcd(d);n = n.divide(gcd);d = d.divide(gcd);return new Rational(n.longValue(), d.longValue());}public static Rational multiply(Rational p, Rational q) throws SQLException {if ((p == null) || (q == null)) {return null;}BigInteger n = BigInteger.valueOf(p.getNumerator()).multiply(BigInteger.valueOf(q.getNumerator()));BigInteger d = BigInteger.valueOf(p.getDenominator()).multiply(BigInteger.valueOf(q.getDenominator()));BigInteger gcd = n.gcd(d);n = n.divide(gcd);d = d.divide(gcd);return new Rational(n.longValue(), d.longValue());}
和
CREATE FUNCTION javatest.rational_negate(javatest.rational) RETURNS javatest.rationalAS 'sandbox.Rational.negate'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_add(javatest.rational, javatest.rational)RETURNS javatest.rationalAS 'sandbox.Rational.add'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_subtract(javatest.rational, javatest.rational)RETURNS javatest.rationalAS 'sandbox.Rational.subtract'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_multiply(javatest.rational, javatest.rational)RETURNS javatest.rationalAS 'sandbox.Rational.multiply'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_divide(javatest.rational, javatest.rational)RETURNS javatest.rationalAS 'sandbox.Rational.divide'LANGUAGE JAVA IMMUTABLE STRICT;CREATE OPERATOR - (rightarg = javatest.rational, procedure.rational_negate);CREATE OPERATOR + (leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_add,commutator = + );CREATE OPERATOR - (leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_subtract);CREATE OPERATOR * (leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_divide,commutator = *);CREATE OPERATOR / (leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_divide);
运算符字符是“ + – * / <> =〜!”中的1到63个字符。 @#%^&| “?” 有一些限制,以避免与SQL注释的开头混淆。
换向运算符是第二个运算符(可能是相同的),如果交换左值和右值,其结果相同。 由优化器使用。
否定运算符是一个如果相反交换左值和右值的结果。 它仅对返回布尔值的过程有效。 再次由优化器使用。
订购运营商
可以通过某种方式订购许多UDT。 这可能是显而易见的事情,例如,对有理数进行排序,或者是更加任意的事情,例如,对复数进行排序。
我们可以按照与上述相同的方式定义排序操作。 注意,这些运算符不再有特殊之处–对于不熟悉的UDT,您不能假设<的真正含义是“小于”。 唯一的例外是“!=”,它始终由解析器重写为“”。
public static int compare(Rational p, Rational q) {if (p == null) {return 1;} else if (q == null) {return -1;}BigInteger l = BigInteger.valueOf(p.getNumerator()).multiply(BigInteger.valueOf(q.getDenominator()));BigInteger r = BigInteger.valueOf(q.getNumerator()).multiply(BigInteger.valueOf(p.getDenominator()));return l.compareTo(r);}public int compareTo(Rational p) {return compare(this, p);}public static int compare(Rational p, double q) {if (p == null) {return 1;}double d = p.doubleValue();return (d < q) ? -1 : ((d == q) ? 0 : 1);}public int compareTo(double q) {return compare(this, q);}public static boolean lessThan(Rational p, Rational q) {return compare(p, q) < 0;}public static boolean lessThanOrEquals(Rational p, Rational q) {return compare(p, q) <= 0;}public static boolean equals(Rational p, Rational q) {return compare(p, q) = 0;}public static boolean greaterThan(Rational p, Rational q) {return compare(p, q) > 0;}public static boolean lessThan(Rational p, double q) {if (p == null) {return false;}return p.compareTo(q) < 0;}public static boolean lessThanOrEquals(Rational p, double q) {if (p == null) {return false;}return p.compareTo(q) = 0;}public static boolean greaterThan(Rational p, double q) {if (p == null) {return true;}return p.compareTo(q) > 0;}
请注意,我已经定义了比较两个有理数或一个有理数和一个双数的方法。
CREATE FUNCTION javatest.rational_lt(javatest.rational, javatest.rational)RETURNS boolAS 'sandbox.Rational.lessThan'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_le(javatest.rational, javatest.rational)RETURNS boolAS 'sandbox.Rational.lessThanOrEquals'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_eq(javatest.rational, javatest.rational)RETURNS boolAS 'sandbox.Rational.equals'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_ge(javatest.rational, javatest.rational)RETURNS boolAS 'sandbox.Rational.greaterThanOrEquals'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_gt(javatest.rational, javatest.rational)RETURNS boolAS 'sandbox.Rational.greaterThan'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_cmp(javatest.rational, javatest.rational)RETURNS intAS 'sandbox.Rational.compare'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_lt(javatest.rational, float8)RETURNS boolAS 'sandbox.Rational.lessThan'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_le(javatest.rational, float8)RETURNS boolAS 'sandbox.Rational.lessThanOrEquals'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_eq(javatest.rational, float8)RETURNS boolAS 'sandbox.Rational.equals'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_ge(javatest.rational, float8)RETURNS boolAS 'sandbox.Rational.greaterThanOrEquals'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_gt(javatest.rational, float8)RETURNS boolAS 'sandbox.Rational.greaterThan'LANGUAGE JAVA IMMUTABLE STRICT;CREATE OPERATOR < (leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_lt,commutator = > , negator = >= ,restrict = scalarltsel, join = scalarltjoinsel, merges);CREATE OPERATOR <= (leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_le,commutator = >= , negator = > , restrict = scalarltsel, join = scalarltjoinsel, merges);CREATE OPERATOR = (leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_eq,commutator = = , negator = <>, hashes, merges);CREATE OPERATOR >= (leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_lt,commutator = <= , negator = < ,restrict = scalarltsel, join = scalarltjoinsel, merges);CREATE OPERATOR > (leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_le,commutator = <= , negator = < , restrict = scalargtsel, join = scalargtjoinsel, merges);CREATE OPERATOR < (leftarg = javatest.rational, rightarg = float8, procedure = javatest.rational_lt,commutator = > , negator = >= );CREATE OPERATOR <= (leftarg = javatest.rational, rightarg = float8, procedure = javatest.rational_le,commutator = >= , negator = > );CREATE OPERATOR = (leftarg = javatest.rational, rightarg = float8, procedure = javatest.rational_eq,commutator = = , negator = <> );CREATE OPERATOR >= (leftarg = javatest.rational, rightarg = float8, procedure = javatest.rational_ge,commutator = <= , negator = <);CREATE OPERATOR > (leftarg = javatest.rational, rightarg = float8, procedure = javatest.rational_gt,commutator = < , negator = <=);
限制是优化估计程序。 通常使用适当的标准过程是安全的。
Join是一个优化估计器过程。 通常使用适当的标准过程是安全的。
哈希表示该运算符可用于哈希联接。
合并表示可以在合并联接中使用运算符。
指标
索引在三个地方使用–强制执行唯一性约束并加快WHERE和JOIN子句。
-- btree joinCREATE OPERATOR CLASS rational_opsDEFAULT FOR TYPE javatest.rational USING btree ASOPERATOR 1 < ,OPERATOR 2 <= ,OPERATOR 3 = ,OPERATOR 4 >= ,OPERATOR 5 > ,FUNCTION 1 javatest.rational_cmp(javatest.rational, javatest.rational);-- hash joinCREATE OPERATOR CLASS rational_opsDEFAULT FOR TYPE javatest.rational USING hash ASOPERATOR 1 = ,FUNCTION 1 javatest.rational_hashCode(javatest.rational);
运营商家庭
最后,PostgreSQL具有“操作者家族”的概念,该概念将相关的操作者类别归为一类。 例如,您可能有一个家族支持int2,int4和int8值之间的交叉比较。 每个都可以单独指定,但是通过创建一个运算符族,您可以给PostgreSQL优化器更多提示。
更多信息
- 创建类型(PostgreSQL)
- PostgreSQL的“创建触发器”文档 。
- PostgreSQL的“创建规则”文档 。 Java
- 创建运算符(PostgreSQL)
- 创建操作员类(PostgreSQL)
- 创建操作员家庭(PostgreSQL)
- 运算符优化(PostgreSQL)
- 连接索引扩展(PostreSQL)
- 用Java创建标量UDT (用户指南)
- CREATE AGGREGATE文档(PostgreSQL)
- 创建CAST文档(PostgreSQL)
- 创建类型文档(PostgreSQL)
- 创建操作员文档(PostgreSQL)
- 创建操作员类文档(PostgreSQL)
- 将用户定义的类型与索引接口(PostgreSQL)
参考: PostgreSQL PL / Java简介,第1部分 , PostgreSQL PL / Java简介,第2部分:使用列表 , PostgreSQL PL / Java简介,第3部分:触发器 , PostgreSQL PL / Java简介,第4部分:用户定义类型 , PostgreSQL / PLJava简介,第5部分:我们的JCG合作伙伴 Bear Giles在Invariant Properties博客上提供。
翻译自: https://www.javacodegeeks.com/2012/10/introduction-to-postgresql-pljava.html