PostgreSQL PL / java简介
PL / Java的安裝
在Ubuntu系統(tǒng)上安裝PL / Java很簡(jiǎn)單。 我將首先創(chuàng)建一個(gè)新模板template_java ,因此我仍然可以創(chuàng)建沒(méi)有pl / java擴(kuò)展名的數(shù)據(jù)庫(kù)。
在命令行上,假設(shè)您是數(shù)據(jù)庫(kù)超級(jí)用戶(hù),請(qǐng)輸入
# 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
局限性
預(yù)包裝的Ubuntu軟件包使用Gnu GCJ Java實(shí)現(xiàn),而不是標(biāo)準(zhǔn)的OpenJDK或Sun實(shí)現(xiàn)。 GCJ將Java源文件編譯為本機(jī)目標(biāo)代碼,而不是字節(jié)碼。 PL / Java的最新版本是“受信任的” –可以依靠它們保留在其沙箱中。 除其他外,這意味著您無(wú)法訪問(wèn)服務(wù)器上的文件系統(tǒng)。
如果必須打破信任關(guān)系,則可以使用第二種語(yǔ)言“ javaU”。 不受信任的函數(shù)只能創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)超級(jí)用戶(hù)。
更重要的是,此實(shí)現(xiàn)是單線程的。 如果您需要與其他服務(wù)器通信,請(qǐng)記住這一點(diǎn)至關(guān)重要。
需要考慮的事情是是否要使用GCJ編譯自己的常用庫(kù),并將它們作為共享庫(kù)加載到PostgreSQL服務(wù)器中。 共享庫(kù)位于/usr/lib/postgresql/9.1/lib中 ,稍后我可能要說(shuō)更多。
快速驗(yàn)證
通過(guò)編寫(xiě)快速測(cè)試功能,我們可以輕松地檢查安裝。 使用template_java創(chuàng)建臨時(shí)數(shù)據(jù)庫(kù),然后輸入以下SQL:
CREATE FUNCTION getsysprop(VARCHAR) RETURNS VARCHARAS 'java.lang.System.getProperty'LANGUAGE java;SELECT getsysprop('user.home'); 結(jié)果,您應(yīng)該得到“ / var / lib / postgresql”。
安裝我們自己的方法
這是一個(gè)不錯(cuò)的開(kāi)始,但是如果我們不能調(diào)用自己的方法,那么我們并不會(huì)真正受益。 幸運(yùn)的是,添加我們自己的并不難。
一個(gè)簡(jiǎn)單的PL / Java過(guò)程是
package sandbox;public class PLJava {public static String hello(String name) {if (name == null) {return null;}return 'Hello, ' + name + '!';} }實(shí)現(xiàn)PL / Java過(guò)程的方法有兩個(gè)簡(jiǎn)單的規(guī)則:
- 它們必須是公共靜態(tài)的
- 如果任何參數(shù)為空 ,他們必須返回null
而已。
將Java類(lèi)導(dǎo)入PostgreSQL服務(wù)器很簡(jiǎn)單。 假設(shè)包類(lèi)在/tmp/sandbox.jar中,而我們啟用Java的數(shù)據(jù)庫(kù)是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');--記住要設(shè)置類(lèi)路徑,這一點(diǎn)很重要。 庫(kù)在卸載時(shí)會(huì)自動(dòng)從類(lèi)路徑中刪除,但安裝后不會(huì)自動(dòng)添加到類(lèi)路徑中。
我們還沒(méi)有完全完成–我們?nèi)匀恍枰獙⑿鹿δ芨嬖V系統(tǒng)。
-- -- create function -- CREATE FUNCTION mydb.hello(varchar) RETURNS varcharAS 'sandbox.PLJava.hello'LANGUAGE java;-- -- drop this function -- DROP FUNCTION mydb.hello(varchar);--現(xiàn)在,我們可以以與其他任何存儲(chǔ)過(guò)程相同的方式調(diào)用我們的java方法。
部署描述符
這里令人頭疼–在安裝庫(kù)時(shí)必須顯式創(chuàng)建函數(shù),而在刪除庫(kù)時(shí)將其刪除。 除了最簡(jiǎn)單的情況之外,這都是耗時(shí)且容易出錯(cuò)的。
幸運(yùn)的是,有一個(gè)解決此問(wèn)題的方法-部署描述符。 精確的格式由ISO / IEC 9075-13:2003定義,但是一個(gè)簡(jiǎn)單的示例就足夠了。
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文件中告知部署人員有關(guān)部署描述符的信息。 一個(gè)示例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> 現(xiàn)在,數(shù)據(jù)庫(kù)將在安裝和刪除我們的方法時(shí)知道它們。
內(nèi)部查詢(xún)
存儲(chǔ)過(guò)程的“大贏家”之一是查詢(xún)是在服務(wù)器本身上執(zhí)行的,比通過(guò)編程接口運(yùn)行查詢(xún)要快得多。 我已經(jīng)看到了一個(gè)過(guò)程,只需將查詢(xún)到的循環(huán)從客戶(hù)端移動(dòng)到服務(wù)器,就可以通過(guò)Java花費(fèi)30分鐘以上將其縮短至不到一秒的時(shí)間。
內(nèi)部連接的JDBC URL是“ jdbc:default:connection”。 您不能使用事務(wù)(因?yàn)槟幱诤艚姓叩氖聞?wù)之內(nèi)),但是只要您停留在單個(gè)呼叫中,就可以使用保存點(diǎn)。 我不知道您是否可以使用CallableStatements(還有其他存儲(chǔ)過(guò)程)–您無(wú)法使用1.2版,但Ubuntu 11.10軟件包使用1.4.2版。
標(biāo)量值列表在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關(guān)鍵字,因?yàn)榇撕瘮?shù)將始終返回相同的值。 這允許數(shù)據(jù)庫(kù)執(zhí)行緩存和查詢(xún)優(yōu)化。
在開(kāi)始之前,您不需要知道結(jié)果,甚至不需要知道結(jié)果的大小。 以下是被認(rèn)為總是會(huì)終止的序列,但尚未得到證實(shí)。 (不幸的是,我忘記了序列的名稱(chēng)。)作為一個(gè)旁注,這不是一個(gè)完整的解決方案,因?yàn)樗粰z查溢出-正確的實(shí)現(xiàn)應(yīng)對(duì)此進(jìn)行檢查或使用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; 在所有條件都相同的情況下,最好根據(jù)需要?jiǎng)?chuàng)建每個(gè)結(jié)果。 如果查詢(xún)具有LIMIT子句,通常可以減少內(nèi)存占用并避免不必要的工作。
單元組
在ResultSet中返回一個(gè)元組。
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表示有效結(jié)果,返回false表示無(wú)效結(jié)果。 可以用相同的方式將復(fù)雜類(lèi)型傳遞給j??ava方法-它是一個(gè)只讀ResultSet ,只包含一行。
元組列表
返回復(fù)雜值列表需要一個(gè)實(shí)現(xiàn)兩個(gè)接口之一的類(lèi)。
org.postgresql.pljava.ResultSetProvider
當(dāng)可以以編程方式或根據(jù)需要?jiǎng)?chuàng)建結(jié)果時(shí),將使用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
當(dāng)方法使用內(nèi)部查詢(xún)時(shí),通常使用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;
介面
我無(wú)法在標(biāo)準(zhǔn)maven存儲(chǔ)庫(kù)中獲得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; }扳機(jī)
數(shù)據(jù)庫(kù)觸發(fā)器是存儲(chǔ)過(guò)程,該過(guò)程在四個(gè)CRUD(創(chuàng)建-讀取-更新-刪除)操作中的三個(gè)操作之一期間自動(dòng)運(yùn)行。
- 插入 –為觸發(fā)器提供了新值,并且可以修改值或直接禁止操作。
- 更新 -觸發(fā)提供舊 的和新的值。 同樣,它能夠修改值或禁止操作。
- 刪除 –為觸發(fā)器提供舊值。 它不能修改該值,但可以禁止該操作。
觸發(fā)器可以在操作之前或之后運(yùn)行。 如果要修改值,可以在操作之前執(zhí)行觸發(fā)器。 如果要記錄結(jié)果,可以在操作后執(zhí)行它。
典型用法
插入和更新:數(shù)據(jù)驗(yàn)證
插入和更新操作的預(yù)觸發(fā)可用于強(qiáng)制數(shù)據(jù)完整性和一致性。 在這種情況下,結(jié)果要么被接受,要么被禁止操作。
插入和更新:數(shù)據(jù)標(biāo)準(zhǔn)化和消毒
有時(shí)值可能具有多種表示形式,或者可能具有危險(xiǎn)性。 預(yù)觸發(fā)是清理數(shù)據(jù)的機(jī)會(huì),例如整理XML或?qū)?lt;替換為<和>替換為>。
所有操作:審核日志記錄
所有操作的后觸發(fā)可用于強(qiáng)制執(zhí)行審核日志記錄。 應(yīng)用程序可以記錄自己的操作,但不能記錄對(duì)數(shù)據(jù)庫(kù)的直接訪問(wèn)。 這是解決此問(wèn)題的方法。
可以為每一行或在完成整個(gè)語(yǔ)句后運(yùn)行觸發(fā)器。 更新觸發(fā)器也可以是有條件的。
觸發(fā)器可用于創(chuàng)建“可更新視圖”。
PL / Java實(shí)現(xiàn)
可以在觸發(fā)器中使用任何java方法,只要它是一個(gè)返回void且使用單個(gè)參數(shù)( TriggerData對(duì)象)的公共靜態(tài)方法即可。 觸發(fā)器可以稱(chēng)為“按行”或“按狀態(tài)”。
“按行”的TriggerData包含一個(gè)單行只讀ResultSet,作為更新和刪除時(shí)的“舊”值,以及一個(gè)單行可更新ResultSet,作為插入和更新時(shí)的“新”值。 這可用于修改內(nèi)容,記錄操作等。
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();
規(guī)則
PostgreSQL擴(kuò)展是Rules 。 它們與觸發(fā)器類(lèi)似,但更加靈活。 一個(gè)重要的區(qū)別是,可以在SELECT語(yǔ)句上觸發(fā)規(guī)則,而不僅僅是INSERT,UPDATE和DELETE。
規(guī)則與觸發(fā)器不同,使用標(biāo)準(zhǔn)函數(shù)。
介面
和以前一樣,我找不到最新版本的Maven存儲(chǔ)庫(kù),為了方便起見(jiàn),我還包含了這些文件。
觸發(fā)數(shù)據(jù)
// 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);} }數(shù)據(jù)庫(kù)中用戶(hù)定義的類(lèi)型存在爭(zhēng)議。 它們不是標(biāo)準(zhǔn)的-在某些時(shí)候DBA必須創(chuàng)建它們-這就引入了可移植性問(wèn)題。 標(biāo)準(zhǔn)工具對(duì)此一無(wú)所知。 您必須通過(guò)ResultSets和PreparedStatements中的“ struct”方法訪問(wèn)它們。
另一方面,還有很多東西只能通過(guò)byte []支持。 這樣可以防止數(shù)據(jù)庫(kù)函數(shù)和存儲(chǔ)過(guò)程輕松地操作它們。
什么是好的用戶(hù)定義類(lèi)型? 它必須是原子的,并且必須可以通過(guò)存儲(chǔ)過(guò)程來(lái)完成有意義的工作。 注意,數(shù)據(jù)庫(kù)用戶(hù)定義類(lèi)型與Java類(lèi)不是同一回事。 幾乎所有Java類(lèi)都應(yīng)存儲(chǔ)為標(biāo)準(zhǔn)元組,并且只有在有充分理由的情況下才應(yīng)使用數(shù)據(jù)庫(kù)UDT。
我喜歡的試金石是詢(xún)問(wèn)您是否曾經(jīng)想過(guò)除了對(duì)象本身之外還緩存有關(guān)類(lèi)型(而不是元組)的不變信息。 例如,X.509數(shù)字證書(shū)具有許多不可變字段,這些字段將是有效的搜索詞,但是為每一行提取該信息非常昂貴。 (旁注:插入和更新記錄時(shí),可以使用觸發(fā)器來(lái)提取信息。這可確保緩存的值始終準(zhǔn)確。)
例子:
- 復(fù)數(shù)(存儲(chǔ)過(guò)程:算術(shù))
- 有理數(shù)(存儲(chǔ)過(guò)程:算術(shù))
- galois字段編號(hào)(存儲(chǔ)過(guò)程:算術(shù)運(yùn)算為固定值)
- 圖像(存儲(chǔ)過(guò)程:獲取尺寸)
- PDF文檔(存儲(chǔ)過(guò)程:提取元素)
- 數(shù)字證書(shū)和私鑰(存儲(chǔ)過(guò)程:加密)
還應(yīng)該解決的是正確的實(shí)施語(yǔ)言。 在PL / Java中創(chuàng)建原型很容易,但是您可以提出一個(gè)強(qiáng)有力的論點(diǎn),即類(lèi)型最終應(yīng)實(shí)現(xiàn)為標(biāo)準(zhǔn)的PostgreSQL擴(kuò)展,因?yàn)閷?lái)您有20歲的歷史時(shí),它們很可能會(huì)出現(xiàn)傾倒。 在某些重要方面,這只是問(wèn)題的一小部分-問(wèn)題不在于實(shí)際的存儲(chǔ)和函數(shù)實(shí)現(xiàn)是用C還是Java編寫(xiě)的,而是它與系統(tǒng)其余部分的聯(lián)系方式。
PL / Java實(shí)現(xiàn)
PL / Java用戶(hù)定義的類(lèi)型必須實(shí)現(xiàn)java.sql.SQLData接口,從字符串創(chuàng)建對(duì)象的靜態(tài)方法以及從對(duì)象創(chuàng)建字符串的實(shí)例方法。 這些方法必須相輔相成–必須有可能在整個(gè)方向上沿任一方向運(yùn)行值并將其取回原始值。
注意,雙打通常是不可能的–這就是為什么您得到數(shù)字4.000000001或2.999999999的原因。 在這些情況下,您將盡力而為并警告用戶(hù)。
在許多情況下,可以以二進(jìn)制格式更有效地存儲(chǔ)對(duì)象。 在PostgreSQL術(shù)語(yǔ)中,這些是TOAST類(lèi)型。 這是通過(guò)實(shí)現(xiàn)兩個(gè)與SQLInput和SQLOutput流一起使用的新方法來(lái)處理的。
接下來(lái)是有理類(lèi)型的簡(jiǎn)單實(shí)現(xiàn)。
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);
類(lèi)型修飾符
PostgreSQL允許類(lèi)型具有修飾符。 示例在“ varchar(200)”或“ numeric(8,2)”中。
PL / Java當(dāng)前不支持此功能(通過(guò)'typmod_in'和'typmod_out'方法),但是我已經(jīng)提交了一個(gè)請(qǐng)求。
演員表
如果您所能做的就是將值存儲(chǔ)和檢索為不透明對(duì)象,則自定義類(lèi)型并不是特別有用。 為什么不使用bytea并完成它呢?
實(shí)際上,在許多UDT中,能夠?qū)DT強(qiáng)制轉(zhuǎn)換為其他類(lèi)型是有意義的。 像復(fù)數(shù)或有理數(shù)之類(lèi)的數(shù)字類(lèi)型應(yīng)該能夠與標(biāo)準(zhǔn)整數(shù)和浮點(diǎn)數(shù)字類(lèi)型相互轉(zhuǎn)換(盡管有限制)。
這應(yīng)該克制。
強(qiáng)制轉(zhuǎn)換作為單參數(shù)靜態(tài)方法實(shí)現(xiàn)。 在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表示,如果任何參數(shù)為NULL,則該函數(shù)將返回NULL。這允許數(shù)據(jù)庫(kù)進(jìn)行一些優(yōu)化。)
(旁注:如果java對(duì)象也是不可變的,我們可能只能使用IMMUTABLE標(biāo)志。我們可能應(yīng)該使Rational對(duì)象不可變,因?yàn)槠渌麛?shù)字類(lèi)型是不可變的。)
匯總功能
min()呢? 有理數(shù)是數(shù)字類(lèi)型,因此它們不應(yīng)該支持所有標(biāo)準(zhǔn)的聚合函數(shù)嗎?
定義新的聚合函數(shù)很簡(jiǎn)單。 簡(jiǎn)單的集合函數(shù)只需要一個(gè)帶有兩個(gè)UDT值并返回一個(gè)的靜態(tài)成員函數(shù)。 通過(guò)最大值,最小值,總和,乘積等可以很容易地看出這一點(diǎn)。更復(fù)雜的聚合需要包含狀態(tài)信息的輔助UDT,采用一個(gè)狀態(tài)UDT和一個(gè)UDT并返回狀態(tài)UDT的靜態(tài)方法,以及確定方法,采用最終狀態(tài)UDT并產(chǎn)生結(jié)果。 用平均值很容易看到–您需要一個(gè)包含計(jì)數(shù)器和運(yùn)行總和的狀態(tài)類(lèi)型。
以下是前一種類(lèi)型的聚合函數(shù)的一些示例。
// 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用戶(hù)定義類(lèi)型和Hibernate用戶(hù)定義類(lèi)型。 警告:休眠代碼是特定于數(shù)據(jù)庫(kù)的。
這是休眠的用戶(hù)定義類(lèi)型。 PostgreSQL 9.1不支持STRUCT類(lèi)型,而是使用字符串。 我們不必使用PL / Java用戶(hù)定義的數(shù)據(jù)類(lèi)型來(lái)執(zhí)行封送處理,但它可以確保一致性。 DbRationalType是上面的Rational類(lèi)。 可以在兩個(gè)地方使用相同的類(lèi),但是會(huì)將對(duì)Hibernate接口的依賴(lài)性引入PL / Java類(lèi)。 如果您從Hibernate源代碼中提取單個(gè)接口,這可能是可以接受的。
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>經(jīng)營(yíng)者
運(yùn)算符是普通的PL / Java方法,也通過(guò)CREATE OPERATOR語(yǔ)句標(biāo)記為運(yùn)算符。
支持有理數(shù)的基本算法為
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);運(yùn)算符字符是“ + – * / <> =?!”中的1到63個(gè)字符。 @#%^&| “?” 有一些限制,以避免與SQL注釋的開(kāi)頭混淆。
換向運(yùn)算符是第二個(gè)運(yùn)算符(可能是相同的),如果交換左值和右值,其結(jié)果相同。 由優(yōu)化器使用。
否定運(yùn)算符是一個(gè)如果相反交換左值和右值的結(jié)果。 它僅對(duì)返回布爾值的過(guò)程有效。 再次由優(yōu)化器使用。
訂購(gòu)運(yùn)營(yíng)商
可以通過(guò)某種方式訂購(gòu)許多UDT。 這可能是顯而易見(jiàn)的事情,例如,對(duì)有理數(shù)進(jìn)行排序,或者是更加任意的事情,例如,對(duì)復(fù)數(shù)進(jìn)行排序。
我們可以按照與上述相同的方式定義排序操作??。 注意,這些運(yùn)算符不再有特殊之處–對(duì)于不熟悉的UDT,您不能假設(shè)<的真正含義是“小于”。 唯一的例外是“!=”,它始終由解析器重寫(xiě)為“”。
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;}請(qǐng)注意,我已經(jīng)定義了比較兩個(gè)有理數(shù)或一個(gè)有理數(shù)和一個(gè)雙數(shù)的方法。
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 = <=);限制是優(yōu)化估計(jì)程序。 通常使用適當(dāng)?shù)臉?biāo)準(zhǔn)過(guò)程是安全的。
Join是一個(gè)優(yōu)化估計(jì)器過(guò)程。 通常使用適當(dāng)?shù)臉?biāo)準(zhǔn)過(guò)程是安全的。
哈希表示該運(yùn)算符可用于哈希聯(lián)接。
合并表示可以在合并聯(lián)接中使用運(yùn)算符。
指標(biāo)
索引在三個(gè)地方使用–強(qiáng)制執(zhí)行唯一性約束并加快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);
運(yùn)營(yíng)商家庭
最后,PostgreSQL具有“操作者家族”的概念,該概念將相關(guān)的操作者類(lèi)別歸為一類(lèi)。 例如,您可能有一個(gè)家族支持int2,int4和int8值之間的交叉比較。 每個(gè)都可以單獨(dú)指定,但是通過(guò)創(chuàng)建一個(gè)運(yùn)算符族,您可以給PostgreSQL優(yōu)化器更多提示。
更多信息
- 創(chuàng)建類(lèi)型(PostgreSQL)
- PostgreSQL的“創(chuàng)建觸發(fā)器”文檔 。
- PostgreSQL的“創(chuàng)建規(guī)則”文檔 。 Java
- 創(chuàng)建運(yùn)算符(PostgreSQL)
- 創(chuàng)建操作員類(lèi)(PostgreSQL)
- 創(chuàng)建操作員家庭(PostgreSQL)
- 運(yùn)算符優(yōu)化(PostgreSQL)
- 連接索引擴(kuò)展(PostreSQL)
- 用Java創(chuàng)建標(biāo)量UDT (用戶(hù)指南)
- CREATE AGGREGATE文檔(PostgreSQL)
- 創(chuàng)建CAST文檔(PostgreSQL)
- 創(chuàng)建類(lèi)型文檔(PostgreSQL)
- 創(chuàng)建操作員文檔(PostgreSQL)
- 創(chuàng)建操作員類(lèi)文檔(PostgreSQL)
- 將用戶(hù)定義的類(lèi)型與索引接口(PostgreSQL)
參考: PostgreSQL PL / Java簡(jiǎn)介,第1部分 , ? PostgreSQL PL / Java簡(jiǎn)介,第2部分:使用列表 , ? PostgreSQL PL / Java簡(jiǎn)介,第3部分:觸發(fā)器 , ? PostgreSQL PL / Java簡(jiǎn)介,第4部分:用戶(hù)定義類(lèi)型 , PostgreSQL / PLJava簡(jiǎn)介,第5部分:我們的JCG合作伙伴 Bear Giles在Invariant Properties博客上提供。
翻譯自: https://www.javacodegeeks.com/2012/10/introduction-to-postgresql-pljava.html
總結(jié)
以上是生活随笔為你收集整理的PostgreSQL PL / java简介的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 已预告抵押和已备案的区别(已预告抵押和已
- 下一篇: 数据库和Webapp安全