pl/postgresql_PostgreSQL PL / java简介

pl/postgresql

现代数据库允许以多种语言编写存储过程。 一种常见的实现语言是java.NB,本文讨论了PostgreSQL特定的Java实现。 其他数据库的详细信息将有所不同,但概念将相同。

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表示无效结果。 可以用相同的方式将复杂类型传递给j​​ava方法-它是一个只读的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用户定义类型。 警告:Hibernate代码是特定于数据库的。

这是Hibernate的用户定义类型。 PostgreSQL 9.1不支持STRUCT类型,而是使用字符串。 我们不必使用PL / Java用户定义的数据类型来执行封送处理,但它可以确保一致性。 DbRationalType是上面的Rational类。 可以在两个地方使用相同的类,但是会在PL / Java类中引入对Hibernate接口的依赖。 如果您从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

pl/postgresql

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/352522.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python输出文本和值_python读取文本中数据并转化为DataFrame的实例

在技术问答中看到一个这样的问题&#xff0c;感觉相对比较常见&#xff0c;就单开一篇文章写下来。 从纯文本格式文件 “file_in”中读取数据&#xff0c;格式如下&#xff1a;需要输出成“file_out”&#xff0c;格式如下&#xff1a;数据的原格式是“类别&#xff1a;内容”&…

欧拉回路与欧拉道路

图G的一个回路&#xff0c;若它恰通过G中每条边一次,则称该回路为欧拉(Euler)回路。如果一个图只是形成一个连通所有节点的链&#xff0c;且每一点只走一次&#xff0c;则成为欧拉道路。具有欧拉回路或欧拉道路的图称为欧拉图&#xff08;简称E图&#xff09;。有向图的欧拉回路…

小心DLL链接静态库时的内存错误

最近写的模块&#xff0c;在独立的应用程序中测试是没问题的&#xff0c;但把它装配成DLL后&#xff0c;再在另一个应用程序中调用时却出现了内存错误。程序的模块链接关系大概是这样的&#xff1a; module就是我所写的模块&#xff0c;在这里被封装为DLL&#xff0c;因为要使用…

阿里试用,女朋友逼着我给她排序

阿里试用排序 抱歉&#xff0c;之前莫名其妙把配置文件给 ignore 了&#xff0c;已经修复&#xff0c;抱歉 前景提要 说来简直丢尽了钢铁直男的脸&#xff0c;没错&#xff0c;昨晚我在愉快的做着外包的活&#xff08;中国移动的小程序&#xff0c;自由职业&#xff0c;喂&…

用于Elasticsearch成绩单的Java客户端

在本演讲中&#xff0c;我将介绍用于Elasticsearch和Spring Data Elasticsearch的三个不同的客户端。 首先&#xff0c;让我们看一下Elasticsearch的一些基础知识。 弹性搜索 为了介绍elasticsearch&#xff0c;我使用的定义直接来自Elastic网站。 Elasticsearch是基于JSON的…

springmvc是什么_当一个http请求来临时,SpringMVC究竟偷偷帮你做了什么?

前文Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架&#xff0c;通过把Model&#xff0c;View&#xff0c;Controller分离&#xff0c;将web层进行职责解耦&#xff0c;把复杂的web应用分成逻辑清晰的几部分&#xff0c;简化开发&#xff0c;减少出…

怎么用python画饼状图_Python入门进阶:Python绘制饼图到Microsoft Excel

原标题&#xff1a;Python入门进阶&#xff1a;Python绘制饼图到Microsoft Excel 来自&#xff1a;Linux迷https://www.linuxmi.com/python-pie-chart-microsoft-excel.html 在本文中&#xff0c;我想向您展示使用Python向Microsoft Excel绘制饼图&#xff0c;为此我们将使用Xl…

Linux系统Shutdown命令定时关机详解

Linux系统下的shutdown命令用于安全的关闭/重启计算机&#xff0c;它不仅可以方便的实现定时关机&#xff0c;还可以由用户决定关机时的相关参数。在执行shutdown命令时&#xff0c;系统会给每个终端&#xff08;用户&#xff09;发送一条屏显&#xff0c;提示关机操作。定时关…

_ASSERTE(_CrtIsValidHeapPointer(pUserData))错误详解

可能原因&#xff1a;DLL和EXE主程序使用的不是同一个堆造成。 解决办法&#xff1a; 1. 采用谁分配谁释放的原则&#xff1b; 2. 绕过 new 和 delete&#xff0c;使用 GlovalAlloc 和 GlobalFree&#xff1b; 3. 更改工程选项&#xff0c; release 版本肯定不会出现这个失败&a…

【机器学习】【线性回归】梯度下降

文章目录 [toc]数据集实际值估计值估计误差代价函数学习率参数更新Python实现导包数据预处理迭代过程数据可视化完整代码 线性拟合结果代价结果 个人主页&#xff1a;丷从心 系列专栏&#xff1a;机器学习 数据集 ( x ( i ) , y ( i ) ) , i 1 , 2 , ⋯ , m \left(x^{(i)} , …

python api是什么_Python/C API

Python/C API Python/C API可能是被最广泛使用的方法。它不仅简单&#xff0c;而且可以在C代码中操作你的Python对象。 这种方法需要以特定的方式来编写C代码以供Python去调用它。所有的Python对象都被表示为一种叫做PyObject的结构体&#xff0c;并且Python.h头文件中提供了各…

Sublime Text插件列表

本文由伯乐在线 - 艾凌风 翻译&#xff0c;黄利民 校稿。英文出处&#xff1a;ipestov.com。欢迎加入翻译组。本文收录了作者辛苦收集的Sublime Text最佳插件&#xff0c;很全。 最佳的Sublime Text 插件 朋友们你们好&#xff01;我尝试着收集了最佳的ST插件&#xff0c;这些插…

C语言sendto()函数:经socket传送数据

相关函数&#xff1a;send, sendmsg, recv, recvfrom, socket头文件&#xff1a;#include <sys/types.h> #include <sys/socket.h>定义函数&#xff1a;int sendto(int s, const void * msg, int len, unsigned int flags, const struct sockaddr * to, int tole…

javaone_JavaOne 2012:向上,向上和向外:使用Akka扩展软件

javaone在晚些时候的社区主题演讲之后&#xff0c;我前往希尔顿金门大桥3/4/5观看了维克多巴生 &#xff08; Viktor Klang &#xff09;的&#xff08; Typesafe &#xff09;“向上&#xff0c;向上和向外&#xff1a;Akka”演讲。 巴生&#xff08;Klang&#xff09;是Akka的…

Windows平台下动态链接库的总结

1、 动态链接库与静态连接库 静态连接库与动态链接库都是经过编译器编译之后的&#xff0c;在计算机上可以直接运行的二进制目标文件&#xff0c;就像exe文件一样&#xff0c;但不同于exe文件的是静态链接库和动态链接库不可以独立运行&#xff0c;一般而言&#xff0c;动态链接…

python建模分析实操_城市公交站点设置优化模型-基于Python

城市公交站点设置的优化分析 一、模型应用 进入21世纪以来&#xff0c;我国城市公共交通飞速发展&#xff0c;然而随着经济社会发展&#xff0c;城市不断升级以及人民生活品质越来越好&#xff0c;城市交通拥堵、出行不便等问题日益突出&#xff0c;严重损坏了市民日常的生活体…

【递归:把目录下所有文件的绝对路径给输出在控制台】

package com.bornsoft.test.capitalpool.tyc;import java.io.File;/*** author shusheng* description* Email shushengyiji.com* date 2018/10/16 10:26*/ public class DiGuiDemo2 {/***需求&#xff1a;请大家把目录下所有文件的绝对路径给输出在控制台*分析&#xff1a;*A:…

UDP sendto和recvfrom使用详解

在网络编程中&#xff0c;UDP运用非常广泛。很多网络协议是基于UDP来实现的&#xff0c;如SNMP等。大家常常用到的局域网文件传输软件飞鸽传书也是基于UDP实现的。 本篇文章跟大家分享linux下UDP的使用和实现&#xff0c;主要介绍下sendto()和recvfrom()两个函数的使用&#xf…

SpringOne Platform 2016回顾

我最近结束了在拉斯维加斯参加SpringOne Platform会议的总结。 这是我第一次参加SpringOne。 这是聆听演讲并与软件开发领域的一些顶级专家进行对话的一种体验。 如果您没有参加SpringOne&#xff0c;那么您肯定会想要阅读这篇文章。 我们将介绍这四个主题&#xff0c;以及如何…

欧姆龙cp1hum读保护解密步骤_欧姆龙PLC的NJ系列NJ产品功能介绍

欧姆龙PLC的NJ 系列NJ运动、逻辑和视觉集于一体欧姆龙PLC的NJ 系列NJ特点One Machine Control运动、逻辑和视觉集于一体将组成机械所需的各种控制设备汇集于一体&#xff0c;使用一个软件即可进行控制。 这就是Sysmac自动化平台的努力目标。 我们的新型机器自动化控制器NJ系列通…