PostgreSQL PL / java簡介

現代數據庫允許以多種語言編寫存儲過程。 一種常見的實現語言是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用戶定義類型。 警告:休眠代碼是特定于數據庫的。

這是休眠的用戶定義類型。 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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/370986.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/370986.shtml
英文地址,請注明出處:http://en.pswp.cn/news/370986.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

強連通分量 圓桌騎士

題目描述 圓桌騎士是一個非常吸引人的職業。因此&#xff0c;在最近幾年里&#xff0c;亞瑟王史無前例的擴招圓桌騎士&#xff0c;并不令人驚訝。現在這里有許多圓桌騎士&#xff0c;每個圓桌騎士都收到一份珍貴的邀請函&#xff0c;被邀請去英靈殿圓桌。這些騎士將要環繞著坐在…

微信小程序echarts層級太高

項目中因為需求&#xff0c;底部的tab導航欄是自己寫的&#xff0c;在開發者工具中一切正常&#xff1b;但是在真機上頁面滑動時&#xff0c;echarts的層級比tab高&#xff0c;調過兩者的z-index后仍然如此。 經過查找后發現cover-view和cover-image替換tab的view后&#xff0…

php解密 碼表,php拼音碼表的生成

php拼音碼表的生成發布于 2014-09-07 11:12:52 | 90 次閱讀 | 評論: 0 | 來源: 網友投遞PHP開源腳本語言PHP(外文名: Hypertext Preprocessor&#xff0c;中文名&#xff1a;“超文本預處理器”)是一種通用開源腳本語言。語法吸收了C語言、Java和Perl的特點&#xff0c;入門門檻…

angular js 使用pdf.js_排名靠前的幾個JS框架發展趨勢和前景

轉載自&#xff1a;葡萄城官網&#xff0c;葡萄城為開發者提供專業的開發工具、解決方案和服務&#xff0c;賦能開發者。原文出處&#xff1a;https://blog.bitsrc.io/top-5-javascript-frameworks-past-present-and-future-8b6fda39de02隨著信息技術領域的發展&#xff0c;企業…

工廠設計模式案例研究

我有一份工作來檢查我們的項目代碼質量。 如果我在項目中發現任何障礙&#xff0c;必須將其報告給我的團隊負責人。 我發現了很多漏洞&#xff0c;我認為可以在博客上進行討論。 不是嘲笑作者&#xff0c;而是一起學習和改進自己。 像這段代碼一樣&#xff0c;這是我在我們的代…

【javascript】DOM操作方法(3)——document節點屬性

document.doctype //document.documentElement //來獲取html元素 document.defaultView //返回document對象所在的window對象 document.body //返回當前文檔的<body>節點 document.head //返回當前文檔的<head>節點 document.activeElement //返回當前文…

debian dhcp服務啟動不了_DHCP服務器配置

DHCP &#xff1d; Dynamic Host Configuration Protocol 基于TCP/IP&#xff0c;用于動態配置工作站網絡接口&#xff0c;使工作站的網絡接口管理自動化。DHCP服務器軟件dhcpd網站&#xff1a;http://www.isc.org安裝方法&#xff1a;#tar -zxvf dhcp-4.0.0.tar.gz#cd dhcp-4.…

澤西島的JSON模式生成

因此&#xff0c;在上一篇文章中&#xff0c;我討論了一個允許在WADL中使用JSON-Schema的建議&#xff0c;這篇文章探討了如何使它與最近構建的Jersey一起使用。 在1.16發布之前&#xff0c;您將必須下載/參考1.16SNAPSHOT。 如果您使用的是Maven&#xff0c;那么假設您已經有…

C++map類型 之 簡單介紹

一&#xff1a;map的前世今生&#xff08;1&#xff09;從關聯容器與順序容器說起。關聯容器通過鍵&#xff08;key&#xff09;存儲和讀取元素。而順序容器則通過元素在容器中的位置順序存儲和訪問元素&#xff08;vector,queue,stack,list等&#xff09;。關聯容器&#xff0…

MySql Socket 完成數據庫的增查Demo

需求: 利用MySql數據庫結合前端技術完成用戶的注冊(要求不使用Web服務技術),所以 Demo采用Socket技術實現Web通信. 第一部分:數據庫創建 數據庫采用mysql 5.7.18, 數據庫名稱為MyUser, 內部有一張表 user.字段有 Id,UserName,Psd,Tel 第二部分:數據庫連接與Socket通信 創建控…

oracle導數卡死,oracle-審計導數

1、因審計需求&#xff0c;需要將MySQL、Oracle數據庫中需要的表數據導入到SqlSERVER進行審計。2、之前的方法&#xff1a;A. oracle組將表dump下來&#xff0c;進行壓縮&#xff0c;傳送到oracle導數服務器(中轉服務器)&#xff0c;再進行還原&#xff0c;然后修改表結構&…

蘋果桌面主題_看膩了手機自帶的桌面主題,試試這個

在這個看臉的時代&#xff0c;顏值似乎越來越重要了。尤其是我們每天都要看到的手機桌面&#xff0c;如果它的顏值好一點&#xff0c;也許我們的心情會更好&#xff0c;所以有不少人都用手機自帶的主題來美化桌面&#xff0c;但是對于喜歡個性的我們&#xff0c;手機自帶的主題…

Java SE 11:推動Java向前發展

介紹 在我看來&#xff0c;這篇文章提出了Java語言應該如何發展以保持其作為首選語言的地位。 它還提供了一些我喜歡但有時&#xff08;可能永遠不會&#xff09;成為Java一部分的功能&#xff0c;由于我將要解釋的某些原因&#xff0c;這些功能有時我已經愛上了。 我真的很想…

python之property屬性

Property的概念&#xff1a;property是一種特殊的屬性&#xff0c;訪問它時會執行一段功能&#xff08;函數&#xff09;&#xff0c;然后返回值。 import mathclass Circle:def __init__(self,radius):#園的半徑radiusself.radiusradiusproperty#areaproperty(area)def area(s…

Hexo使用細節及各種問題

解決markdown圖片不顯示(返回403 forbidden)、添加本地圖片無法顯示、修改文章page模板、同時部署發布同步到多個倉庫站點(Github、coding、gitee 碼云) 圖片不顯示 在使用過程中&#xff0c;會發現有的引用圖片無法顯示的問題。但是如果直接復制圖片地址到瀏覽器打開的話顯示…

oracle的等保,Oracle等保測評相關指令

Oracle用戶管理:SQL*Pluscreate user 用戶名 identified by 密碼; //創建用戶grant 權限(dba管理員&#xff0c;resource普通用戶&#xff0c;connect訪客) to 用戶名; //授權drop user 用戶名 cascade; //刪除用戶&#xff0c;加cascade會把用戶創建的所有東西刪除Linux設置用…

Spring3 + JPA2 + Java EE6 App Server =配置混亂

Spring很棒&#xff0c;JavaEE6很棒&#xff0c;最新的JavaEE6 Application服務器也很棒。 這篇文章不是Spring Vs JavaEE6上的專欄文章&#xff0c;而是我在JBoss AS-7.1 App Server上移植Spring3 JPA2&#xff08;Hibernate&#xff09;應用程序的經驗。 我的應用程序要求非…

python面向對象進階(1)

面向對象進階 isinstance(obj,cls) 檢查是否obj是類cls的對象class Foo(object): passobj Foo() isinstance(obj,Foo)issubclass(sub,super) 檢查sub是否是super的派生類class Foo(object): passclass Bar(Foo): passissubclass(Bar,Foo) 反射python面向對象中的反射&#xff…

智能小車37:異常在ARM、JAVA、硬件里的實現

幾乎所有編程語言都有異常&#xff0c;可以說有程序就有異常。今天學習Arm的中斷(異常)處理,聯想到Java的異常,硬件中如何實現等問題&#xff0c;下面給大家分享一下。 一、Arm的中斷。 1.觸發異常 2.保存現場 3.cpu進入異常工作模式&#xff0c;程序指針(pc)跳入異常入口&…

c++builder提高批量動態創建panel的速度_騎行時影響速度的事項有哪些 怎樣有效提高騎行速度 單車租賃信息...

撇開人的因素在自行車的組件中對車速影響最大的幾項是什么?車重?自鎖?輪組?傳動?我的個人感受&#xff0c;從提高幅度上來講&#xff0c;而不是重要性上來講一、自鎖起碼提高你50%的速度&#xff0c;我不用自鎖和別人一起走AVS25就很辛苦了&#xff0c;用了自鎖&#xff0…