日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > 数据库 >内容正文

数据库

pl/postgresql_PostgreSQL PL / java简介

發布時間:2023/12/3 数据库 55 豆豆
生活随笔 收集整理的這篇文章主要介紹了 pl/postgresql_PostgreSQL PL / java简介 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

pl/postgresql

現代數據庫允許以多種語言編寫存儲過程。 一種常見的實現語言是java.NB,本文討論了PostgreSQL特定的Java實現。 其他數據庫的詳細信息將有所不同,但概念將相同。

PL / Java的安裝

在Ubuntu系統上安裝PL / Java很簡單。 我將首先創建一個新模板template_java ,因此我仍然可以創建沒有pl / java擴展名的數據庫。

在命令行上,假設您是數據庫超級用戶,請輸入

# apt-get install postgresql-9.1 # apt-get install postgresql-9.1-pljava-gcj$ createdb template_java $ psql -d template_java -c 'update db_database set datistemplate='t' where datnam='template_java'' $ psql -d template_java -f /usr/share/postgresql-9.1-pljava/install.sql


局限性

預包裝的Ubuntu軟件包使用Gnu GCJ Java實現,而不是標準的OpenJDK或Sun實現。 GCJ將Java源文件編譯為本機目標代碼,而不是字節碼。 PL / Java的最新版本是“受信任的” –可以依靠它們保留在其沙箱中。 除其他外,這意味著您無法訪問服務器上的文件系統。

如果必須打破信任關系,則可以使用第二種語言“ javaU”。 不受信任的函數只能創建一個數據庫超級用戶。

更重要的是,此實現是單線程的。 如果您需要與其他服務器通信,請記住這一點至關重要。

需要考慮的事情是是否要使用GCJ編譯自己的常用庫,并將它們作為共享庫加載到PostgreSQL服務器中。 共享庫位于/usr/lib/postgresql/9.1/lib中 ,稍后我可能要說更多。

快速驗證

通過編寫快速測試功能,我們可以輕松地檢查安裝。 使用template_java創建臨時數據庫,然后輸入以下SQL:

CREATE FUNCTION getsysprop(VARCHAR) RETURNS VARCHARAS 'java.lang.System.getProperty'LANGUAGE java;SELECT getsysprop('user.home');

結果,您應該得到“ / var / lib / postgresql”。

安裝我們自己的方法

這是一個不錯的開始,但是如果我們不能調用自己的方法,那么我們并不會真正受益。 幸運的是,添加我們自己的并不難。

一個簡單的PL / Java過程是

package sandbox;public class PLJava {public static String hello(String name) {if (name == null) {return null;}return 'Hello, ' + name + '!';} }

實現PL / Java過程的方法有兩個簡單的規則:

  • 它們必須是公共靜態的
  • 如果任何參數為空 ,他們必須返回null

而已。

將Java類導入PostgreSQL服務器很簡單。 假設包類在/tmp/sandbox.jar中,而我們啟用Java的數據庫是mydb 。 然后我們的命令是

-- -- load java library -- -- parameters: -- url_path - where the library is located -- url_name - how the library is referred to later -- deploy - should the deployment descriptor be used? -- select sqlj.install_jar('file:///tmp/sandbox.jar', 'sandbox', true);-- -- set classpath to include new library. -- -- parameters -- schema - schema (or database) name -- classpath - colon-separated list of url_names. -- select sqlj.set_classpath('mydb', 'sandbox');-- ------------------- -- other procedures -- -- --------------------- -- reload java library -- select sqlj.replace_jar('file:///tmp/sandbox.jar', 'sandbox', true);-- -- remove java library -- -- parameters: -- url_name - how the library is referred to later -- undeploy - should the deployment descriptor be used? -- select sqlj.remove_jar('sandbox', true);-- -- list classpath -- select sqlj.get_classpath('mydb');--

記住要設置類路徑,這一點很重要。 庫在卸載時會自動從類路徑中刪除,但安裝后不會自動添加到類路徑中。

我們還沒有完全完成–我們仍然需要向系統介紹我們的新功能。

-- -- create function -- CREATE FUNCTION mydb.hello(varchar) RETURNS varcharAS 'sandbox.PLJava.hello'LANGUAGE java;-- -- drop this function -- DROP FUNCTION mydb.hello(varchar);--

現在,我們可以以與其他任何存儲過程相同的方式調用java方法。

部署描述符

這里令人頭疼–在安裝庫時必須顯式創建函數,而在刪除庫時將其刪除。 除了最簡單的情況,這都是耗時且容易出錯的。

幸運的是,有一個解決此問題的方法-部署描述符。 精確的格式由ISO / IEC 9075-13:2003定義,但是一個簡單的示例就足夠了。

SQLActions[] = {'BEGIN INSTALLCREATE FUNCTION javatest.hello(varchar)RETURNS varcharAS 'sandbox.PLJava.hello'LANGUAGE java;END INSTALL','BEGIN REMOVEDROP FUNCTION javatest.hello(varchar);END REMOVE' }

您必須在jar的MANIFEST.MF文件中告訴部署人員有關部署描述符的信息。 一個示例Maven插件是

<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><version>2.3.1</version><configuration><archive><manifestSections><manifestSection><name>postgresql.ddr</name> <!-- filename --><manifestEntries><SQLJDeploymentDescriptor>TRUE</SQLJDeploymentDescriptor></manifestEntries></manifestSection></manifestSections></archive></configuration> </plugin>

現在,數據庫將在安裝和刪除我們的方法時知道它們。

內部查詢

存儲過程的“大贏家”之一是查詢是在服務器本身上執行的,比通過編程接口運行查詢要快得多。 我已經看到了一個過程,只需將查詢到的循環從客戶端移動到服務器,就需要通過Java花費30分鐘以上的時間,從而縮短了不到一秒的時間。

內部連接的JDBC URL是“ jdbc:default:connection”。 您不能使用事務(因為您處于呼叫者的事務之內),但是只要您停留在單個呼叫中,就可以使用保存點。 我不知道您是否可以使用CallableStatements(還有其他存儲過程)–您無法使用1.2版,但Ubuntu 11.10軟件包使用1.4.2版。

標量值列表在Java世界中以迭代器的形式返回 ,在SQL世界中以SETOF的形式返回 。

public static Iterator<String> colors() {List<String> colors = Arrays.asList('red', 'green', 'blue');return colors.iterator();}

CREATE FUNCTION javatest.colors()RETURNS SETOF varcharAS 'sandbox.PLJava.colors'IMMUTABLE LANGUAGE java;

我添加了IMMUTABLE關鍵字,因為此函數將始終返回相同的值。 這允許數據庫執行緩存和查詢優化。

在開始之前,您不需要知道結果,甚至不需要知道結果的大小。 以下是被認為總是會終止的序列,但這尚未得到證明。 (不幸的是,我忘記了序列的名稱。)作為一個旁注,這不是一個完整的解決方案,因為它不檢查是否存在溢出-正確的實現應對此進行檢查或使用BigInteger。

public static Iterator seq(int start) {Iterator iter = null;try {iter = new SeqIterator(start);} catch (IllegalArgumentException e) {// should log error...}return iter;}public static class SeqIterator implements Iterator {private int next;private boolean done = false;public SeqIterator(int start) {if (start <= 0) {throw new IllegalArgumentException();}this.next = start;}@Overridepublic boolean hasNext() {return !done;}@Overridepublic Integer next() {int value = next;next = (next % 2 == 0) ? next / 2 : 3 * next + 1;done = (value == 1);return value;}@Overridepublic void remove() {throw new UnsupportedOperationException();}}CREATE FUNCTION javatest.seq(int)RETURNS SETOF intAS 'sandbox.PLJava.seq'IMMUTABLE LANGUAGE java;

在所有條件都相同的情況下,最好根據需要創建每個結果。 如果查詢具有LIMIT子句,通常可以減少內存占用并避免不必要的工作。

單元組

在ResultSet中返回一個元組。

public static boolean singleWord(ResultSet receiver) throws SQLException {receiver.updateString('English', 'hello');receiver.updateString('Spanish', 'hola');return true;}

CREATE TYPE word AS (English varchar,Spanish varchar);CREATE FUNCTION javatest.single_word()RETURNS wordAS 'sandbox.PLJava.singleWord'IMMUTABLE LANGUAGE java;

返回true表示有效結果,返回false表示無效結果。 可以用相同的方式將復雜類型傳遞給j??ava方法-它是一個只讀的ResultSet ,只包含一行。

元組列表

返回復雜值列表需要一個實現兩個接口之一的類。

org.postgresql.pljava.ResultSetProvider

當可以以編程方式或根據需要創建結果時,將使用ResultSetProvider 。

public static ResultSetProvider listWords() {return new WordProvider();}public static class WordProvider implements ResultSetProvider {private final Map<String,String> words = new HashMap<String,String>();private final Iterator<String> keys;public WordProvider() {words.put('one', 'uno');words.put('two', 'dos');words.put('three', 'tres');words.put('four', 'quatro');keys = words.keySet().iterator();}@Overridepublic boolean assignRowValues(ResultSet receiver, int currentRow)throws SQLException {if (!keys.hasNext()) {return false;}String key = keys.next();receiver.updateString('English', key);receiver.updateString('Spanish', words.get(key));return true;}@Overridepublic void close() throws SQLException {}}

CREATE FUNCTION javatest.list_words()RETURNS SETOF wordAS 'sandbox.PLJava.listWords'IMMUTABLE LANGUAGE java;

org.postgresql.pljava.ResultSetHandle

當方法使用內部查詢時,通常使用ResultSetHandle 。

public static ResultSetHandle listUsers() {return new UsersHandle();}public static class UsersHandle implements ResultSetHandle {private Statement stmt;@Overridepublic ResultSet getResultSet() throws SQLException {stmt = DriverManager.getConnection('jdbc:default:connection').createStatement();return stmt.executeQuery('SELECT * FROM pg_user');}@Overridepublic void close() throws SQLException {stmt.close();} }

CREATE FUNCTION javatest.list_users()RETURNS SETOF pg_userAS 'sandbox.PLJava.listUsers'LANGUAGE java;


介面

我無法在標準maven存儲庫中獲得pljava jar的最新副本。 我的解決方案是從PL / Java源tarball提取接口。 為了方便您在此處提供它們。

ResultSetProvider

// Copyright (c) 2004, 2005, 2006 TADA AB - Taby Sweden// Distributed under the terms shown in the file COPYRIGHT// found in the root folder of this project or at// http://eng.tada.se/osprojects/COPYRIGHT.htmlpackage org.postgresql.pljava;import java.sql.ResultSet; import java.sql.SQLException;// An implementation of this interface is returned from functions and procedures// that are declared to return <code>SET OF</code> a complex type. //Functions that// return <code>SET OF</code> a simple type should simply return an// {@link java.util.Iterator Iterator}.// @author Thomas Hallgrenpublic interface ResultSetProvider {// This method is called once for each row that should be returned from// a procedure that returns a set of rows. The receiver// is a {@link org.postgresql.pljava.jdbc.SingleRowWriter SingleRowWriter}// writer instance that is used for capturing the data for the row.// @param receiver Receiver of values for the given row.// @param currentRow Row number. First call will have row number 0.// @return <code>true</code> if a new row was provided, <code>false</code>// if not (end of data).// @throws SQLExceptionboolean assignRowValues(ResultSet receiver, int currentRow)throws SQLException;// Called after the last row has returned or when the query evaluator dec ides// that it does not need any more rows.//void close()throws SQLException; }

ResultSetHandle

// Copyright (c) 2004, 2005, 2006 TADA AB - Taby Sweden // Distributed under the terms shown in the file COPYRIGHT // found in the root directory of this distribution or at // http://eng.tada.se/osprojects/COPYRIGHT.htmlpackage org.postgresql.pljava;import java.sql.ResultSet; import java.sql.SQLException;// An implementation of this interface is returned from functions and procedures// that are declared to return <code>SET OF</code> a complex type in the form// of a {@link java.sql.ResultSet}. The primary motivation for this interface is// that an implementation that returns a ResultSet must be able to close the// connection and statement when no more rows are requested.// @author Thomas Hallgrenpublic interface ResultSetHandle {// An implementation of this method will probably execute a query// and return the result of that query.// @return The ResultSet that represents the rows to be returned.// @throws SQLExceptionResultSet getResultSet()throws SQLException;// Called after the last row has returned or when the query evaluator decides// that it does not need any more rows.void close()throws SQLException; }

扳機

數據庫觸發器是存儲過程,該過程在四個CRUD(創建-讀取-更新-刪除)操作中的三個操作之一期間自動運行。

  • 插入 –為觸發器提供了新值,并且可以修改值或直接禁止操作。
  • 更新 -觸發提供舊 的和新的值。 同樣,它能夠修改值或禁止操作。
  • 刪除 –為觸發器提供舊值。 它不能修改該值,但可以禁止該操作。

觸發器可以在操作之前或之后運行。 如果要修改值,可以在操作之前執行觸發器。 如果要記錄結果,可以在操作后執行它。

典型用法

插入和更新:數據驗證

插入和更新操作的預觸發可用于強制數據完整性和一致性。 在這種情況下,結果要么被接受,要么被禁止操作。

插入和更新:數據標準化和消毒

有時值可能具有多種表示形式,或者可能具有危險性。 預觸發是清理數據的機會,例如整理XML或將<替換為<和>替換為>。

所有操作:審核日志記錄

所有操作的后觸發可用于強制執行審核日志記錄。 應用程序可以記錄自己的操作,但不能記錄對數據庫的直接訪問。 這是解決此問題的方法。

可以為每一行或在完成整個語句后運行觸發器。 更新觸發器也可以是有條件的。

觸發器可用于創建“可更新視圖”。

PL / Java實現

可以在觸發器中使用任何java方法,只要它是一個返回void且使用單個參數( TriggerData對象)的公共靜態方法即可。 觸發器可以稱為“按行”或“按狀態”。

“在每個行上”的TriggerData包含一個單行只讀ResultSet作為更新和刪除時的“舊”值,以及一個單行可更新ResultSet作為插入和更新時的“新”值。 這可用于修改內容,記錄操作等。

public class AuditTrigger {public static void auditFoobar(TriggerData td) throws SQLException {Connection conn = DriverManager.getConnection('jdbc:default:connection');PreparedStatement ps = conn.prepareStatement('insert into javatest.foobar_audit(what, whenn, data) values (?, ?, ?::xml)');if (td.isFiredByInsert()) {ps.setString(1, 'INSERT');} else if (td.isFiredByUpdate()) {ps.setString(1, 'UPDATE');} else if (td.isFiredByDelete()) {ps.setString(1, 'DELETE');}ps.setTimestamp(2, new Timestamp(System.currentTimeMillis()));ResultSet rs = td.getNew();if (rs != null) {ps.setString(3, toXml(rs));} else {ps.setNull(3, Types.VARCHAR);}ps.execute();ps.close();}// simple marshaler. We could use jaxb or similar librarystatic String toXml(ResultSet rs) throws SQLException {String foo = rs.getString(1);if (rs.wasNull()) {foo = '';}String bar = rs.getString(2);if (rs.wasNull()) {bar = '';}return String.format('<my-class><foo>%s</foo><bar>%s</bar></my-class>', foo, bar);} }CREATE TABLE javatest.foobar (foo varchar(10),bar varchar(10));CREATE TABLE javatest.foobar_audit (what varchar(10) not null,whenn timestamp not null,data xml);CREATE FUNCTION javatest.audit_foobar()RETURNS triggerAS 'sandbox.AuditTrigger.auditFoobar'LANGUAGE 'java';CREATE TRIGGER foobar_auditAFTER INSERT OR UPDATE OR DELETE ON javatest.foobarFOR EACH ROWEXECUTE PROCEDURE javatest.audit_foobar();


規則

PostgreSQL擴展是Rules 。 它們與觸發器相似,但更加靈活。 一個重要的區別是,可以在SELECT語句上觸發規則,而不僅僅是INSERT,UPDATE和DELETE。

規則與觸發器不同,使用標準函數。

介面

和以前一樣,我找不到最新版本的Maven存儲庫,為了方便起見,我還包括了這些文件。

觸發數據

// Copyright (c) 2004, 2005, 2006 TADA AB - Taby Sweden// Distributed under the terms shown in the file COPYRIGHT// found in the root folder of this project or at// http://eng.tada.se/osprojects/COPYRIGHT.htmlpackage org.postgresql.pljava;import java.sql.ResultSet; import java.sql.SQLException;// The SQL 2003 spec. does not stipulate a standard way of mapping// triggers to functions. The PLJava mapping use this interface. All// functions that are intended to be triggers must be public, static,// return void, and take a <code>TriggerData</code> as their argument.// // @author Thomas Hallgrenpublic interface TriggerData {// Returns the ResultSet that represents the new row. This ResultSet wil// be null for delete triggers and for triggers that was fired for// statement. //The returned set will be updateable and positioned on a// valid row. When the trigger call returns, the trigger manager will se// the changes that has been made to this row and construct a new tuple// which will become the new or updated row.//// @return An updateable <code>ResultSet</code> containing one row or// null// @throws SQLException// if the contained native buffer has gone stale.//ResultSet getNew() throws SQLException;// Returns the ResultSet that represents the old row. This ResultSet wil// be null for insert triggers and for triggers that was fired for// statement.The returned set will be read-only and positioned on a// valid row.// // @return A read-only ResultSet containing one row or// null.// @throws SQLException// if the contained native buffer has gone stale.//ResultSet getOld() throws SQLException;//// Returns the arguments for this trigger (as declared in the <code>CREAT // E TRIGGER</code>// statement. If the trigger has no arguments, this method will return an// array with size 0.// // @throws SQLException// if the contained native buffer has gone stale.String[] getArguments() throws SQLException;// Returns the name of the trigger (as declared in theCREATE TRIGGER// statement).//// @throws SQLException// if the contained native buffer has gone stale.//String getName() throws SQLException; /** //Returns the name of the table for which this trigger was created (as //* declared in the <code>CREATE TRIGGER</code statement). * * @throws SQLException* if the contained native buffer has gone stale. String getTableName() throws SQLException; /// Returns the name of the schema of the table for which this trigger was created (as * declared in the <code>CREATE TRIGGER</code statement). //@throws SQLException * if the contained native buffer has gone stale. */String getSchemaName() throws SQLException; // Returns <code>true</code> if the trigger was fired after the statement or row action that it is associated with. //@throws SQLException * if the contained native buffer has gone stale. boolean isFiredAfter() throws SQLException; //Returns <code>true</code> if the trigger was fired before the * //statement or row action that it is associated with. * * @throws SQLException * if //the contained native buffer has gone stale. */ boolean isFiredBefore() throws SQLException; //Returns <code>true</code> if this trigger is fired once for each row * //(as opposed to once for the entire statement). * * @throws SQLException * if the //contained native buffer has gone stale. */ boolean isFiredForEachRow() throws SQLException; //Returns <code>true</code> if this trigger is fired once for the entire //statement (as opposed to once for each row). * * @throws SQLException * if the //contained native buffer has gone stale. */ boolean isFiredForStatement() throws SQLException; //Returns <code>true</code> if this trigger was fired by a <code>DELETE</code>. * * @throws SQLException * if the contained native //buffer has gone stale. */ boolean isFiredByDelete() throws SQLException; //Returns <code>true</code> if this trigger was fired by an //<code>INSERT</code>. * * @throws SQLException * if the contained native //buffer has gone stale. */ boolean isFiredByInsert() throws SQLException; //Returns <code>true</code> if this trigger was fired by an //<code>UPDATE</code>. * * @throws SQLException * if the contained native //buffer has gone stale. */ boolean isFiredByUpdate() throws SQLException; // Returns the name of the table for which this trigger was created (as // declared in the <code>CREATE TRIGGER</code statement). * * @throws //SQLException* if the contained native buffer has gone stale. */ String getTableName() throws SQLException; // Returns the name of the schema of the table for which this trigger was created (as / declared in the <code>CREATE TRIGGER</code statement). * * @throws //SQLException * if the contained native buffer has gone stale. */ String getSchemaName() throws SQLException; //Returns <code>true</code> if the trigger was fired after the statement // or row action that it is associated with. * * @throws SQLException * if the //contained native buffer has gone stale. */ boolean isFiredAfter() throws SQLException; // Returns <code>true</code> if the trigger was fired before the * //statement or row action that it is associated with. * * @throws SQLException * if //the contained native buffer has gone stale. */ boolean isFiredBefore() throws SQLException; // Returns <code>true</code> if this trigger is fired once for each row * //(as opposed to once for the entire statement). * * @throws SQLException * if the //contained native buffer has gone stale. */ boolean isFiredForEachRow() throws SQLException; // Returns <code>true</code> if this trigger is fired once for the entire // statement (as opposed to once for each row). * * @throws SQLException * if the //contained native buffer has gone stale. */ boolean isFiredForStatement() throws SQLException; // Returns <code>true</code> if this trigger was fired by a //<code>DELETE</code>. * * @throws SQLException * if the contained native //buffer has gone stale. */ boolean isFiredByDelete() throws SQLException; // Returns <code>true</code> if this trigger was fired by an //<code>INSERT</code>. * * @throws SQLException * if the contained native //buffer has gone stale. */ boolean isFiredByInsert() throws SQLException; // Returns <code>true</code> if this trigger was fired by an //<code>UPDATE</code>. * * @throws SQLException * if the contained native //buffer has gone stale. */ boolean isFiredByUpdate() throws SQLException; }/** // Returns the name of the table for which this trigger was created (as // declared in the <code>CREATE TRIGGER</code statement). * * @throws //SQLException* if the contained native buffer has gone stale. */ String getTableName() throws SQLException; // Returns the name of the schema of the table for which this trigger was created (as // declared in the <code>CREATE TRIGGER</code statement). * * @throws //SQLException * if the contained native buffer has gone stale. */ String getSchemaName() throws SQLException; /// Returns <code>true</code> if the trigger was fired after the //statement * or row action that it is associated with. * * @throws SQLException * if //the contained native buffer has gone stale. */ boolean isFiredAfter() throws SQLException; // Returns <code>true</code> if the trigger was fired before the * //statement or row action that it is associated with. * * @throws SQLException * if //the contained native buffer has gone stale. */ boolean isFiredBefore() throws SQLException; // Returns <code>true</code> if this trigger is fired once for each row * (//as opposed to once for the entire statement). * * @throws SQLException * if the //contained native buffer has gone stale. */ boolean isFiredForEachRow() throws SQLException; // Returns <code>true</code> if this trigger is fired once for the entire // statement (as opposed to once for each row). * * @throws SQLException * if the //contained native buffer has gone stale. */ boolean isFiredForStatement() throws SQLException; // Returns <code>true</code> if this trigger was fired by a //<code>DELETE</code>. * * @throws SQLException * if the contained native //buffer has gone stale. */ boolean isFiredByDelete() throws SQLException; // Returns <code>true</code> if this trigger was fired by an //<code>INSERT</code>. * * @throws SQLException * if the contained native //buffer has gone stale. */ boolean isFiredByInsert() throws SQLException; // Returns <code>true</code> if this trigger was fired by an //<code>UPDATE</code>. * * @throws SQLException * if the contained native //buffer has gone stale. */ boolean isFiredByUpdate() throws SQLException; }

TriggerException

// Copyright (c) 2004, 2005, 2006 TADA AB - Taby Sweden// Distributed under the terms shown in the file COPYRIGHT// found in the root folder of this project or at// http://eng.tada.se/osprojects/COPYRIGHT.htmlpackage org.postgresql.pljava;import java.sql.SQLException;// An exception specially suited to be thrown from within a method// designated to be a trigger function. The message generated by// this exception will contain information on what trigger and// what relation it was that caused the exception// // @author Thomas Hallgrenpublic class TriggerException extends SQLException {private static final long serialVersionUID = 5543711707414329116L;private static boolean s_recursionLock = false;public static final String TRIGGER_ACTION_EXCEPTION = '09000';private static final String makeMessage(TriggerData td, String message){StringBuffer bld = new StringBuffer();bld.append('In Trigger ');if(!s_recursionLock){s_recursionLock = true;try{bld.append(td.getName());bld.append(' on relation ');bld.append(td.getTableName());}catch(SQLException e){bld.append('(exception while generating exception message)');}finally{s_recursionLock = false;}}if(message != null){bld.append(': ');bld.append(message);}return bld.toString();}// Create an exception based on the <code>TriggerData</code> that was// passed to the trigger method.// @param td The <code>TriggerData</code> that was passed to the trigger// method.public TriggerException(TriggerData td){super(makeMessage(td, null), TRIGGER_ACTION_EXCEPTION);}// Create an exception based on the <code>TriggerData</code> that was// passed to the trigger method and an additional message.// @param td The <code>TriggerData</code> that was passed to the trigger// method.// @param reason An additional message with info about the exception.public TriggerException(TriggerData td, String reason){super(makeMessage(td, reason), TRIGGER_ACTION_EXCEPTION);} }

數據庫中用戶定義的類型存在爭議。 它們不是標準的-在某些時候DBA必須創建它們-這就引入了可移植性問題。 標準工具對此一無所知。 您必須通過ResultSets和PreparedStatements中的“ struct”方法訪問它們。

另一方面,還有很多東西,否則只能作為byte []支持。 這樣可以防止數據庫函數和存儲過程輕松地操作它們。

什么是好的用戶定義類型? 它必須是原子的,并且必須有可能通過存儲過程來完成有意義的工作。 注意,數據庫用戶定義類型與Java類不是同一回事。 幾乎所有Java類都應存儲為標準元組,并且只有在有充分理由的情況下才應使用數據庫UDT。

我喜歡的試金石是詢問您是否曾經想過除了對象本身之外還緩存有關類型(而不是元組)的不變信息。 例如,X.509數字證書具有許多不可變字段,這些字段將是有效的搜索詞,但是為每一行提取該信息非常昂貴。 (旁注:插入和更新記錄時,可以使用觸發器來提取信息。這可以確保緩存的值始終準確。)

例子:

  • 復數(存儲過程:算術)
  • 有理數(存儲過程:算術)
  • galois字段號(存儲過程:對固定值進行求模運算)
  • 圖像(存儲過程:獲取尺寸)
  • PDF文檔(存儲過程:提取元素)
  • 數字證書和私鑰(存儲過程:加密)

還應該解決的是正確的實施語言。 在PL / Java中創建原型很容易,但是您可以提出一個強有力的論點,即類型最終應實現為標準的PostgreSQL擴展,因為將來您使用20歲以上時,它們很可能會出現傾倒。 在某些重要方面,這只是問題的一小部分–問題不在于實際的存儲和函數實現是用C還是Java編寫的,而是它與系統其余部分的聯系方式。

PL / Java實現

PL / Java用戶定義的類型必須實現java.sql.SQLData接口,從字符串創建對象的靜態方法以及從對象創建字符串的實例方法。 這些方法必須相輔相成–必須有可能在一個方向上以一個完整的周期運行一個值,并取回原始值。

注意,雙打通常是不可能的–這就是為什么您會得到4.000000001或2.999999999之類的數字的原因。 在這些情況下,您將盡力而為并警告用戶。

在許多情況下,可以以二進制格式更有效地存儲對象。 在PostgreSQL術語中,這些是TOAST類型。 這是通過實現兩個與SQLInput和SQLOutput流一起使用的新方法來處理的。

接下來是有理類型的簡單實現。

public class Rational implements SQLData {private long numerator;private long denominator;private String typeName;public static Rational parse(String input, String typeName)throws SQLException {Pattern pattern = Pattern.compile('(-?[0-9]+)( */ *(-?[0-9]+))?');Matcher matcher = pattern.matcher(input);if (!matcher.matches()) {throw new SQLException('Unable to parse rational from string \'' + input+ ''');}if (matcher.groupCount() == 3) {if (matcher.group(3) == null) {return new Rational(Long.parseLong(matcher.group(1)));}return new Rational(Long.parseLong(matcher.group(1)),Long.parseLong(matcher.group(3)));}throw new SQLException('invalid format: \'' + input+ ''');}public Rational(long numerator) throws SQLException {this(numerator, 1);}public Rational(long numerator, long denominator) throws SQLException {if (denominator == 0) {throw new SQLException('demominator must be non-zero');}// do a little bit of normalizationif (denominator < 0) {numerator = -numerator;denominator = -denominator;}this.numerator = numerator;this.denominator = denominator;}public Rational(int numerator, int denominator, String typeName)throws SQLException {this(numerator, denominator);this.typeName = typeName;}public String getSQLTypeName() {return typeName;}public void readSQL(SQLInput stream, String typeName) throws SQLException {this.numerator = stream.readLong();this.denominator = stream.readLong();this.typeName = typeName;}public void writeSQL(SQLOutput stream) throws SQLException {stream.writeLong(numerator);stream.writeLong(denominator);}public String toString() {String value = null;if (denominator == 1) {value = String.valueOf(numerator);} else {value = String.format('%d/%d', numerator, denominator);}return value;}/** Meaningful code that actually does something with this type was* intentionally left out.*/ }

/* The shell type */CREATE TYPE javatest.rational;/* The scalar input function */CREATE FUNCTION javatest.rational_in(cstring)RETURNS javatest.rationalAS 'UDT[sandbox.Rational] input'LANGUAGE java IMMUTABLE STRICT;/* The scalar output function */CREATE FUNCTION javatest.rational_out(javatest.rational)RETURNS cstringAS 'UDT[sandbox.Rational] output'LANGUAGE java IMMUTABLE STRICT;/* The scalar receive function */CREATE FUNCTION javatest.rational_recv(internal)RETURNS javatest.rationalAS 'UDT[sandbox.Rational] receive'LANGUAGE java IMMUTABLE STRICT;/* The scalar send function */CREATE FUNCTION javatest.rational_send(javatest.rational)RETURNS byteaAS 'UDT[sandbox.Rational] send'LANGUAGE java IMMUTABLE STRICT;CREATE TYPE javatest.rational (internallength = 16,input = javatest.rational_in,output = javatest.rational_out,receive = javatest.rational_recv,send = javatest.rational_send,alignment = int);


類型修飾符

PostgreSQL允許類型具有修飾符。 示例在“ varchar(200)”或“ numeric(8,2)”中。

PL / Java當前不支持此功能(通過'typmod_in'和'typmod_out'方法),但是我已經提交了一個請求。

演員表

如果您所能做的就是將值存儲和檢索為不透明對象,則自定義類型不是特別有用。 為什么不使用bytea并完成它呢?

實際上,在許多UDT中,能夠將UDT強制轉換為其他類型是有意義的。 像復數或有理數之類的數字類型應該能夠與標準整數和浮點數字類型相互轉換(盡管有限制)。

這應該克制。

強制轉換作為單參數靜態方法實現。 在Java世界中,這些方法通常被命名為newInstance,因此我在這里也做同樣的事情。

public static Rational newInstance(String input) throws SQLException {if (input == null) {return null;}return parse(input, 'javatest.rational');}public static Rational newInstance(int value) throws SQLException {return new Rational(value);}public static Rational newInstance(Integer value) throws SQLException {if (value == null) {return null;}return new Rational(value.intValue());}public static Rational newInstance(long value) throws SQLException {return new Rational(value);}public static Rational newInstance(Long value) throws SQLException {if (value == null) {return null;}return new Rational(value.longValue());}public static Double value(Rational value) throws SQLException {if (value == null) {return null;}return value.doubleValue();}

CREATE FUNCTION javatest.rational_string_as_rational(varchar) RETURNS javatest.rationalAS 'sandbox.Rational.newInstance'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_int_as_rational(int4) RETURNS javatest.rationalAS 'sandbox.Rational.newInstance'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_long_as_rational(int8) RETURNS javatest.rationalAS 'sandbox.Rational.newInstance'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_as_double(javatest.rational) RETURNS float8AS 'sandbox.Rational.value'LANGUAGE JAVA IMMUTABLE STRICT;CREATE CAST (varchar AS javatest.rational)WITH FUNCTION javatest.rational_string_as_rational(varchar)AS ASSIGNMENT;CREATE CAST (int4 AS javatest.rational)WITH FUNCTION javatest.rational_int_as_rational(int4)AS ASSIGNMENT;CREATE CAST (int8 AS javatest.rational)WITH FUNCTION javatest.rational_long_as_rational(int8)AS ASSIGNMENT;CREATE CAST (javatest.rational AS float8)WITH FUNCTION javatest.rational_as_double(javatest.rational)AS ASSIGNMENT;

(旁注: STRICT表示,如果任何參數為NULL,該函數將返回NULL。這允許數據庫進行一些優化。)

(旁注:如果java對象也是不可變的,我們可能只能使用IMMUTABLE標志。我們可能應該使Rational對象不可變,因為其他數字類型是不可變的。)

匯總功能

min()呢? 有理數是數字類型,因此它們不應該支持所有標準的聚合函數嗎?

定義新的聚合函數很簡單。 簡單的聚合函數只需要一個靜態成員函數,該成員函數采用兩個UDT值并返回一個。 通過最大值,最小值,總和,乘積等可以很容易地看出這一點。更復雜的聚合需要包含狀態信息的輔助UDT,采用一個狀態UDT和一個UDT并返回狀態UDT的靜態方法,以及確定方法,采用最終狀態UDT并產生結果。 用平均值很容易看到–您需要一個包含計數器和運行總和的狀態類型。

以下是前一種類型的聚合函數的幾個示例。

// compare two Rational objects. We use BigInteger to avoid overflow.public static int compare(Rational p, Rational q) {if (p == null) {return 1;} else if (q == null) {return -1;}BigInteger l = BigInteger.valueOf(p.getNumerator()).multiply(BigInteger.valueOf(q.getDenominator()));BigInteger r = BigInteger.valueOf(q.getNumerator()).multiply(BigInteger.valueOf(p.getDenominator()));return l.compareTo(r);}public static Rational min(Rational p, Rational q) {if ((p == null) || (q == null)) {return null;}return (p.compareTo(q) <= 0) ? p : q;}public static Rational max(Rational p, Rational q) {if ((p == null) || (q == null)) {return null;}return (q.compareTo(p) < 0) ? p : q;}public static Rational add(Rational p, Rational q) throws SQLException {if ((p == null) || (q == null)) {return null;}BigInteger n = BigInteger.valueOf(p.getNumerator()).multiply(BigInteger.valueOf(q.getDenominator())).add(BigInteger.valueOf(q.getNumerator()).multiply(BigInteger.valueOf(p.getDenominator())));BigInteger d = BigInteger.valueOf(p.getDenominator()).multiply(BigInteger.valueOf(q.getDenominator()));BigInteger gcd = n.gcd(d);n = n.divide(gcd);d = d.divide(gcd);return new Rational(n.longValue(), d.longValue());}

CREATE FUNCTION javatest.min(javatest.rational, javatest.rational) RETURNS javatest.rationalAS 'sandbox.Rational.min'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.max(javatest.rational, javatest.rational) RETURNS javatest.rationalAS 'sandbox.Rational.max'LANGUAGE JAVA IMMUTABLE STRICT;CREATE AGGREGATE min(javatest.rational) (sfunc = javatest.min,stype = javatest.rational);CREATE AGGREGATE max(javatest.rational) (sfunc = javatest.max,stype = javatest.rational);CREATE AGGREGATE sum(javatest.rational) (sfunc = javatest.add,stype = javatest.rational);


與Hibernate集成

可以鏈接PL / Java用戶定義類型和Hibernate用戶定義類型。 警告:Hibernate代碼是特定于數據庫的。

這是Hibernate的用戶定義類型。 PostgreSQL 9.1不支持STRUCT類型,而是使用字符串。 我們不必使用PL / Java用戶定義的數據類型來執行封送處理,但它可以確保一致性。 DbRationalType是上面的Rational類。 可以在兩個地方使用相同的類,但是會在PL / Java類中引入對Hibernate接口的依賴。 如果您從Hibernate源代碼中提取單個接口,那么這可能是可以接受的。

public class Rational implements UserType, Serializable {private final int[] sqlTypesSupported = new int[] { Types.OTHER };private long numerator;private long denominator;public Rational() {numerator = 0;denominator = 1;}public Rational(long numerator, long denominator) {this.numerator = numerator;this.denominator = denominator;}public long getNumerator() {return numerator;}public long getDenominator() {return denominator;}@Overridepublic Object assemble(Serializable cached, Object owner)throws HibernateException {if (!(cached instanceof Rational)) {throw new HibernateException('invalid argument');}Rational r = (Rational) cached;return new Rational(r.getNumerator(), r.getDenominator());}@Overridepublic Serializable disassemble(Object value) throws HibernateException {if (!(value instanceof Rational)) {throw new HibernateException('invalid argument');}return (Rational) value;}@Overridepublic Object deepCopy(Object value) throws HibernateException {if (value == null) {return null}if (!(value instanceof Rational)) {throw new HibernateException('invalid argument');}Rational v = (Rational) value;return new Rational(v.getNumerator(), v.getDenominator());}@Overridepublic boolean isMutable() {return true;}//// important: PGobject is postgresql-specific// @Overridepublic Object nullSafeGet(ResultSet rs, String[] names, Object owners)throws HibernateException, SQLException {PGobject pgo = (PGobject) rs.getObject(names[0]);if (rs.wasNull()) {return null;}TheDbRationalType r = TheDbRationalType.parse(pgo.getValue(), 'rational');return new Rational(r.getNumerator(), r.getDenominator());}//// important: using Types.OTHER may be postgresql-specific// @Overridepublic void nullSafeSet(PreparedStatement ps, Object value, int index)throws HibernateException, SQLException {if (value == null) {ps.setNull(index, Types.OTHER);} else if (!(value instanceof Rational)) {throw new HibernateException('invalid argument');} else {Rational t = (Rational) value;ps.setObject(index,new TheDbRationalType(t.getNumerator(), t.getDenominator()), Types.OTHER);}}@Overridepublic Object replace(Object original, Object target, Object owner)throws HibernateException {if (!(original instanceof Rational)|| !(target instanceof Rational)) {throw new HibernateException('invalid argument');}Rational r = (Rational) original;return new Rational(r.getNumerator(), r.getDenominator());}@Overridepublic Class returnedClass() {return Rational.class;}@Overridepublic int[] sqlTypes() {return sqlTypesSupported;}@Overridepublic String toString() {String value = '';if (denominator == 1) {value = String.valueOf(numerator);} else {value = String.format('%d/%d', numerator, denominator);}return value;}// for UserType@Overridepublic int hashCode(Object value) {Rational r = (Rational) value;return (int) (31 * r.getNumerator() + r.getDenominator());}@Overridepublic int hashCode() {return hashCode(this);}// for UserType@Overridepublic boolean equals(Object left, Object right) {if (left == right) {return true;}if ((left == null) || (right == null)) {return false;}if (!(left instanceof Rational) || !(right instanceof Rational)) {return false;}Rational l = (Rational) left;Rational r = (Rational) right;return (l.getNumerator() == r.getNumerator())&& (l.getDenominator() == r.getDenominator());}@Overridepublic boolean equals(Object value) {return equals(this, value);} }

CustomTypes.hbm.xml

<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-mapping PUBLIC'-//Hibernate/Hibernate Mapping DTD 3.0//EN''http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd'><hibernate-mapping><typedef name='javatest.rational' class='sandbox.RationalType'/></hibernate-mapping>

TestTable.hbm.xml

<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-mapping PUBLIC'-//Hibernate/Hibernate Mapping DTD 3.0//EN''http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd'><hibernate-mapping><class name='sandbox.TestTable' table='test_table'><id name='id'/><property name='value' type='javatest.rational' /></class></hibernate-mapping>

經營者

運算符是普通的PL / Java方法,也通過CREATE OPERATOR語句標記為運算符。

支持有理數的基本算法為

public static Rational negate(Rational p) throws SQLException {if (p == null) {return null;}return new Rational(-p.getNumerator(), p.getDenominator());}public static Rational add(Rational p, Rational q) throws SQLException {if ((p == null) || (q == null)) {return null;}BigInteger n = BigInteger.valueOf(p.getNumerator()).multiply(BigInteger.valueOf(q.getDenominator())).add(BigInteger.valueOf(q.getNumerator()).multiply(BigInteger.valueOf(p.getDenominator())));BigInteger d = BigInteger.valueOf(p.getDenominator()).multiply(BigInteger.valueOf(q.getDenominator()));BigInteger gcd = n.gcd(d);n = n.divide(gcd);d = d.divide(gcd);return new Rational(n.longValue(), d.longValue());}public static Rational subtract(Rational p, Rational q) throws SQLException {if ((p == null) || (q == null)) {return null;}BigInteger n = BigInteger.valueOf(p.getNumerator()).multiply(BigInteger.valueOf(q.getDenominator())).subtract(BigInteger.valueOf(q.getNumerator()).multiply(BigInteger.valueOf(p.getDenominator())));BigInteger d = BigInteger.valueOf(p.getDenominator()).multiply(BigInteger.valueOf(q.getDenominator()));BigInteger gcd = n.gcd(d);n = n.divide(gcd);d = d.divide(gcd);return new Rational(n.longValue(), d.longValue());}public static Rational multiply(Rational p, Rational q) throws SQLException {if ((p == null) || (q == null)) {return null;}BigInteger n = BigInteger.valueOf(p.getNumerator()).multiply(BigInteger.valueOf(q.getNumerator()));BigInteger d = BigInteger.valueOf(p.getDenominator()).multiply(BigInteger.valueOf(q.getDenominator()));BigInteger gcd = n.gcd(d);n = n.divide(gcd);d = d.divide(gcd);return new Rational(n.longValue(), d.longValue());}

CREATE FUNCTION javatest.rational_negate(javatest.rational) RETURNS javatest.rationalAS 'sandbox.Rational.negate'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_add(javatest.rational, javatest.rational)RETURNS javatest.rationalAS 'sandbox.Rational.add'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_subtract(javatest.rational, javatest.rational)RETURNS javatest.rationalAS 'sandbox.Rational.subtract'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_multiply(javatest.rational, javatest.rational)RETURNS javatest.rationalAS 'sandbox.Rational.multiply'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_divide(javatest.rational, javatest.rational)RETURNS javatest.rationalAS 'sandbox.Rational.divide'LANGUAGE JAVA IMMUTABLE STRICT;CREATE OPERATOR - (rightarg = javatest.rational, procedure.rational_negate);CREATE OPERATOR + (leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_add,commutator = + );CREATE OPERATOR - (leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_subtract);CREATE OPERATOR * (leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_divide,commutator = *);CREATE OPERATOR / (leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_divide);

操作符字符是從“ + – * / <> =?!”集合中的1到63個字符。 @#%^&| “?” 有一些限制,以避免與SQL注釋的開頭混淆。

換向運算符是第二個運算符(可能是相同的),如果交換左值和右值,其結果相同。 由優化器使用。

否定器操作者是一個相反的結果,如果左和右的值交換。 它僅對返回布爾值的過程有效。 再次由優化器使用。

訂購運營商

可以以某種方式訂購許多UDT。 這可能是很明顯的事情,例如對有理數進行排序,或者是更加任意的事情(例如對復數進行排序)。

我們可以按照與上述相同的方式定義排序操作??。 注意,這些運算符不再有特殊之處–對于不熟悉的UDT,您不能假設<的真正含義是“小于”。 唯一的例外是“!=”,它始終由解析器重寫為“”。

public static int compare(Rational p, Rational q) {if (p == null) {return 1;} else if (q == null) {return -1;}BigInteger l = BigInteger.valueOf(p.getNumerator()).multiply(BigInteger.valueOf(q.getDenominator()));BigInteger r = BigInteger.valueOf(q.getNumerator()).multiply(BigInteger.valueOf(p.getDenominator()));return l.compareTo(r);}public int compareTo(Rational p) {return compare(this, p);}public static int compare(Rational p, double q) {if (p == null) {return 1;}double d = p.doubleValue();return (d < q) ? -1 : ((d == q) ? 0 : 1);}public int compareTo(double q) {return compare(this, q);}public static boolean lessThan(Rational p, Rational q) {return compare(p, q) < 0;}public static boolean lessThanOrEquals(Rational p, Rational q) {return compare(p, q) <= 0;}public static boolean equals(Rational p, Rational q) {return compare(p, q) = 0;}public static boolean greaterThan(Rational p, Rational q) {return compare(p, q) > 0;}public static boolean lessThan(Rational p, double q) {if (p == null) {return false;}return p.compareTo(q) < 0;}public static boolean lessThanOrEquals(Rational p, double q) {if (p == null) {return false;}return p.compareTo(q) = 0;}public static boolean greaterThan(Rational p, double q) {if (p == null) {return true;}return p.compareTo(q) > 0;}

請注意,我已經定義了比較兩個有理數或一個有理數和一個雙數的方法。

CREATE FUNCTION javatest.rational_lt(javatest.rational, javatest.rational)RETURNS boolAS 'sandbox.Rational.lessThan'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_le(javatest.rational, javatest.rational)RETURNS boolAS 'sandbox.Rational.lessThanOrEquals'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_eq(javatest.rational, javatest.rational)RETURNS boolAS 'sandbox.Rational.equals'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_ge(javatest.rational, javatest.rational)RETURNS boolAS 'sandbox.Rational.greaterThanOrEquals'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_gt(javatest.rational, javatest.rational)RETURNS boolAS 'sandbox.Rational.greaterThan'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_cmp(javatest.rational, javatest.rational)RETURNS intAS 'sandbox.Rational.compare'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_lt(javatest.rational, float8)RETURNS boolAS 'sandbox.Rational.lessThan'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_le(javatest.rational, float8)RETURNS boolAS 'sandbox.Rational.lessThanOrEquals'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_eq(javatest.rational, float8)RETURNS boolAS 'sandbox.Rational.equals'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_ge(javatest.rational, float8)RETURNS boolAS 'sandbox.Rational.greaterThanOrEquals'LANGUAGE JAVA IMMUTABLE STRICT;CREATE FUNCTION javatest.rational_gt(javatest.rational, float8)RETURNS boolAS 'sandbox.Rational.greaterThan'LANGUAGE JAVA IMMUTABLE STRICT;CREATE OPERATOR < (leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_lt,commutator = > , negator = >= ,restrict = scalarltsel, join = scalarltjoinsel, merges);CREATE OPERATOR <= (leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_le,commutator = >= , negator = > , restrict = scalarltsel, join = scalarltjoinsel, merges);CREATE OPERATOR = (leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_eq,commutator = = , negator = <>, hashes, merges);CREATE OPERATOR >= (leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_lt,commutator = <= , negator = < ,restrict = scalarltsel, join = scalarltjoinsel, merges);CREATE OPERATOR > (leftarg = javatest.rational, rightarg = javatest.rational, procedure = javatest.rational_le,commutator = <= , negator = < , restrict = scalargtsel, join = scalargtjoinsel, merges);CREATE OPERATOR < (leftarg = javatest.rational, rightarg = float8, procedure = javatest.rational_lt,commutator = > , negator = >= );CREATE OPERATOR <= (leftarg = javatest.rational, rightarg = float8, procedure = javatest.rational_le,commutator = >= , negator = > );CREATE OPERATOR = (leftarg = javatest.rational, rightarg = float8, procedure = javatest.rational_eq,commutator = = , negator = <> );CREATE OPERATOR >= (leftarg = javatest.rational, rightarg = float8, procedure = javatest.rational_ge,commutator = <= , negator = <);CREATE OPERATOR > (leftarg = javatest.rational, rightarg = float8, procedure = javatest.rational_gt,commutator = < , negator = <=);

限制是優化估計程序。 通常使用適當的標準程序是安全的。

Join是一個優化估計程序。 通常使用適當的標準程序是安全的。

哈希表示該運算符可用于哈希聯接。

合并表示可以在合并聯接中使用運算符。

指標

索引在三個地方使用–強制執行唯一性約束并加快WHERE和JOIN子句。

-- btree joinCREATE OPERATOR CLASS rational_opsDEFAULT FOR TYPE javatest.rational USING btree ASOPERATOR 1 < ,OPERATOR 2 <= ,OPERATOR 3 = ,OPERATOR 4 >= ,OPERATOR 5 > ,FUNCTION 1 javatest.rational_cmp(javatest.rational, javatest.rational);-- hash joinCREATE OPERATOR CLASS rational_opsDEFAULT FOR TYPE javatest.rational USING hash ASOPERATOR 1 = ,FUNCTION 1 javatest.rational_hashCode(javatest.rational);


運營商家庭

最后,PostgreSQL具有“操作者家族”的概念,該概念將相關的操作者類別歸為一類。 例如,您可能有一個家族支持int2,int4和int8值之間的交叉比較。 每個都可以單獨指定,但是通過創建一個運算符族,您可以給PostgreSQL優化器更多提示。

更多信息

  • 創建類型(PostgreSQL)
  • PostgreSQL“創建觸發器”文檔 。
  • PostgreSQL“創建規則”文檔 。 Java
  • 創建運算符(PostgreSQL)
  • 創建操作員類(PostgreSQL)
  • 創建操作員家庭(PostgreSQL)
  • 運算符優化(PostgreSQL)
  • 接口索引擴展(PostreSQL)
  • 用Java創建標量UDT (用戶指南)
  • CREATE AGGREGATE文檔(PostgreSQL)
  • 創建CAST文檔(PostgreSQL)
  • 創建類型文檔(PostgreSQL)
  • 創建操作員文檔(PostgreSQL)
  • 創建操作員類文檔(PostgreSQL)
  • 將用戶定義的類型與索引接口(PostgreSQL)

參考: PostgreSQL PL / Java簡介,第1部分 , ? PostgreSQL PL / Java簡介,第2部分:使用列表 , ? PostgreSQL PL / Java簡介,第3部分:觸發器 , ? PostgreSQL PL / Java簡介,第4部分:用戶定義類型 , PostgreSQL / PLJava簡介,第5部分:我們的JCG合作伙伴 Bear Giles在Invariant Properties博客上發表。


翻譯自: https://www.javacodegeeks.com/2012/10/introduction-to-postgresql-pljava.html

pl/postgresql

總結

以上是生活随笔為你收集整理的pl/postgresql_PostgreSQL PL / java简介的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

永久免费视频国产 | 人人射人人 | 国产精品久久久久久久久久东京 | 正在播放日韩 | 婷五月激情 | 国产视频中文字幕 | 91mv.cool在线观看 | 日日夜夜网 | 亚洲aaa毛片 | 又黄又网站 | 色99之美女主播在线视频 | 99c视频高清免费观看 | 国产精品久久久一区二区三区网站 | 91亚色视频| 字幕网资源站中文字幕 | 久久免费在线视频 | 2023av| 五月精品 | 日本黄色特级片 | 国产精品久久久久影视 | 亚洲国产日本 | 伊人狠狠色 | 欧美一级日韩三级 | 亚洲专区在线播放 | 最新日韩电影 | 色黄久久久久久 | 国产成人精品综合久久久 | 丁香久久五月 | 欧美污在线观看 | 91一区二区在线 | 91精品国产91久久久久 | 久久久在线免费观看 | 麻豆91精品91久久久 | 色婷婷六月天 | 日韩成人黄色 | 黄色官网在线观看 | 超级av在线 | 99久精品视频 | 国产成人精品午夜在线播放 | 色噜噜狠狠狠狠色综合久不 | 色综合久久五月天 | 99久久99精品 | 久久精品5| 97香蕉久久国产在线观看 | 精品在线亚洲视频 | 人人舔人人爱 | 亚洲天堂在线观看完整版 | 成年人视频在线免费 | 日韩精品高清不卡 | 成人毛片一区二区三区 | 亚洲综合欧美激情 | 人人玩人人添人人澡超碰 | 国产精品乱码久久 | 在线成人av | 高清不卡一区二区在线 | 最近中文字幕 | 99久久精品免费看国产四区 | 国精产品满18岁在线 | 日韩视频在线播放 | 久久久精品视频网站 | 中文字幕一区二区三区久久蜜桃 | 五月婷婷婷婷婷 | 日韩欧美一区二区在线观看 | 永久免费毛片在线观看 | 黄色成人在线 | 在线www色 | 欧美男同视频网站 | 亚洲视频观看 | 色妞久久福利网 | 日韩av午夜在线观看 | 亚洲春色奇米影视 | 毛片基地黄久久久久久天堂 | 99re8这里有精品热视频免费 | 97超碰人人网 | 人人讲下载 | 国产xxxx性hd极品 | 成人一区二区在线 | 欧洲视频一区 | 亚洲精品国产精品久久99 | 成人97视频一区二区 | 欧美日韩视频观看 | av电影一区二区 | 五月香婷| 免费观看一级特黄欧美大片 | 九九九热精品免费视频观看网站 | 亚洲激情国产精品 | 99久久精品国产毛片 | 亚欧洲精品视频在线观看 | 日日天天狠狠 | 日韩成人精品一区二区三区 | 日日夜夜精品免费视频 | 五月婷婷中文 | 欧美视频不卡 | 久久精品国产免费看久久精品 | 久久久久久久久精 | 日韩免费视频线观看 | 国产日韩精品一区二区在线观看播放 | 99理论片| 国产在线观看你懂得 | 免费黄色一区 | 久久天堂亚洲 | 精品在线一区二区三区 | 在线视频欧美日韩 | 福利视频第一页 | 日本xxxx裸体xxxx17 | 婷婷色av | 欧美成人a在线 | 91社区国产高清 | 超碰人人91 | 日韩性xxxx | 婷婷在线视频 | 干干操操| 一级黄色大片 | 狠狠干在线 | 黄色精品国产 | 欧美视频一区二 | 四虎影视成人精品国库在线观看 | 伊人色播| 一区二区三区动漫 | 中文字幕区 | 精品国产aⅴ一区二区三区 在线直播av | 热久久在线视频 | 亚洲精品免费在线 | 久久五月婷婷丁香 | 久久久999精品视频 国产美女免费观看 | 国产精品成人一区二区 | 日韩中文三级 | www.午夜色.com | 亚洲激情久久 | 超碰久热 | 国产精品区在线观看 | 欧美91视频 | 久久精品播放 | 亚洲精品美女久久17c | av在线免费在线 | 久久人人爽人人 | 国产精品2019 | 色综合久| 最近日本字幕mv免费观看在线 | 色网站中文字幕 | 五月婷婷久 | 久久久毛片 | 在线观看岛国 | 精品伊人久久久 | 亚洲精品国产精品99久久 | 亚洲成av人影院 | 中文字幕一区二区三区乱码不卡 | 天堂av在线网站 | 一区二区av| 午夜精品电影一区二区在线 | 91精品蜜桃 | 91香蕉视频 mp4 | 在线看国产一区 | 亚洲视频电影在线 | 国产美女精品 | 久久久久国产一区二区三区 | 久久免费视频在线观看 | 天堂麻豆 | 在线观看激情av | 精品久久久久久综合日本 | 日韩视| 成人三级黄色 | 日日综合网 | 综合久久影院 | 男女日麻批 | 九色在线视频 | 美女免费视频网站 | 国产精品对白一区二区三区 | 亚洲理论在线观看电影 | 欧美一级片免费播放 | 日韩一二三区不卡 | 最近中文字幕免费 | 久草com | 久久黄网站 | 西西www444| 蜜臀久久99精品久久久无需会员 | 成人av在线电影 | 午夜精品视频在线 | 婷婷av网| 亚洲精品麻豆视频 | 美女视频黄在线观看 | 91色国产| 久久久天天操 | 91av亚洲| 中文av在线免费观看 | 国产精品久久一卡二卡 | 国产日本在线 | 中文字幕成人在线 | 亚洲国产高清视频 | 97在线公开视频 | 成年人视频在线 | 麻豆传媒视频在线免费观看 | 国产高清不卡av | 日韩毛片精品 | 欧美成人aa | 在线看片中文字幕 | 久久香蕉影视 | 婷婷中文在线 | 国产成人福利在线观看 | 欧美精品九九99久久 | 日韩一区二区三区高清免费看看 | 色综合久久综合中文综合网 | www.五月婷 | 最新中文在线视频 | 国内精品久久久久久久影视麻豆 | 福利片视频区 | 狠狠色噜噜狠狠狠狠 | 亚洲国产资源 | 亚色视频在线观看 | 热九九精品| 中文在线8新资源库 | 久草视频在线观 | 综合网在线视频 | 午夜精品一区二区三区免费视频 | 欧美精品在线视频观看 | 久久精品国产99国产 | 国产手机av | 久99视频| 一区电影| 成人a大片 | 久草剧场 | 久草视频在线资源站 | 91麻豆网 | 国产精品美女久久久久久网站 | 视频一区视频二区在线观看 | 亚洲 欧美日韩 国产 中文 | 国产精品 国产精品 | 激情开心站 | 久久精品精品电影网 | 国产黄在线 | 91手机电影 | 成人a v视频 | 成人在线视频免费观看 | 夜夜操夜夜干 | 国产免费高清 | 中文字幕视频 | 91福利国产在线观看 | 麻豆精品在线视频 | 亚洲视频一级 | 中文有码在线视频 | 天天干天天射天天爽 | 天天干天天爽 | 九色视频网 | 中文字幕无吗 | 亚洲影院天堂 | www.干| 69xxxx欧美| 久久黄色免费观看 | 韩国av永久免费 | 91视频麻豆 | 99精品在线观看 | 中文字幕日韩国产 | 91麻豆精品国产91久久久无限制版 | 国产精品美女毛片真酒店 | 国产亚洲精品电影 | 欧美天天综合网 | 五月天婷婷丁香花 | 久久全国免费视频 | 亚洲成人一区 | 国产福利网站 | 五月婷婷色丁香 | 麻豆传媒在线免费看 | 国产欧美精品一区二区三区四区 | 91免费高清在线观看 | 成人免费在线视频 | 日韩理论在线 | 欧美日韩在线看 | 色老板在线视频 | 亚洲国产精品一区二区尤物区 | www.黄色片.com| 99精品视频在线观看播放 | 天天操 夜夜操 | 99久久婷婷国产综合亚洲 | 亚洲视频在线观看网站 | 狠狠干狠狠久久 | 91色视频 | 国产成人av网址 | 久久久久免费精品视频 | 日躁夜躁狠狠躁2001 | 成人一级免费视频 | 国产又粗又猛又黄视频 | 成人h视频在线 | 在线日韩亚洲 | 国产69熟 | 在线免费观看黄色 | 在线免费黄色av | 最近高清中文字幕在线国语5 | 丁香六月婷婷开心 | 亚洲 欧洲 国产 日本 综合 | 伊人久久在线观看 | 又大又硬又黄又爽视频在线观看 | 成人sm另类专区 | 在线观看免费版高清版 | 国产精品久久久久久久毛片 | 日韩精品在线免费观看 | 国产一区二区久久久 | 欧美一级视频免费看 | 中文字幕av在线不卡 | 69久久夜色精品国产69 | 日韩精品一区二区三区高清免费 | 一本到视频在线观看 | 黄色视屏在线免费观看 | 免费看wwwwwwwwwww的视频 久久久久久99精品 91中文字幕视频 | 狠狠色狠狠色终合网 | 国产专区在线播放 | 久久精品国产成人 | av黄色免费网站 | 久久久99精品免费观看app | 99热国内精品 | 国内揄拍国产精品 | www.伊人色.com | 美女福利视频一区二区 | 麻豆视频在线免费看 | 国产码电影 | 亚洲最大av网 | 久久久国产精品久久久 | 综合网天天射 | 国产中文字幕一区二区 | www黄免费 | 日韩av一区二区在线 | 亚洲成人动漫在线观看 | 精品人人人 | 在线天堂中文在线资源网 | 亚洲精品在线视频观看 | 成人在线视频网 | 久久九九久久精品 | 97电影手机| 国产一区不卡在线 | 狠狠色丁香 | 四季av综合网站 | 午夜一级免费电影 | 欧美久久九九 | 黄色软件在线看 | 亚洲精选在线 | 最新午夜 | 国产成人精品一区二区三区福利 | 黄网站大全 | 日韩在线视频精品 | 欧美另类成人 | www.天天色.com | 天堂av在线中文在线 | 久久免费中文视频 | 国产高清不卡在线 | 五月天九九 | 亚洲天堂毛片 | 337p西西人体大胆瓣开下部 | 成人av一区二区兰花在线播放 | 日韩av看片 | 国产18精品乱码免费看 | 在线观看视频你懂的 | 久久99亚洲网美利坚合众国 | 婷婷色站| 69视频国产 | 久久中文字幕在线视频 | 久久亚洲欧美日韩精品专区 | www色,com| 91高清免费在线观看 | 人人插超碰 | 在线播放视频一区 | 成年在线观看 | 久久精彩| 天天操天天干天天玩 | 国产精品综合av一区二区国产馆 | 天天干天天草天天爽 | 亚洲精品女 | 美女视频黄色免费 | 成人影片免费 | 国产无限资源在线观看 | 美女网站视频免费都是黄 | 欧美视频二区 | 久久人人爽人人爽人人片av软件 | 亚洲在线视频免费 | 蜜臀aⅴ国产精品久久久国产 | 亚洲欧美精品一区二区 | 亚洲成a人片77777潘金莲 | 在线观av| 在线观看免费日韩 | 九九九在线观看 | 六月丁香久久 | 亚洲精品www| 欧美日韩不卡一区二区 | av福利网址导航大全 | 日韩黄色在线观看 | av888.com| 麻豆影视在线观看 | 五月综合色 | 日韩欧美成 | 日韩高清一区二区 | 午夜成人影视 | 色婷婷成人网 | 中文字幕韩在线第一页 | 免费看国产一级片 | 精品在线小视频 | 久久久精品欧美一区二区免费 | 亚洲年轻女教师毛茸茸 | 国产91精品久久久久 | 中文日韩在线视频 | 蜜臀aⅴ国产精品久久久国产 | 制服丝袜一区二区 | 成人国产一区 | 久久视频一区二区 | 亚洲国产三级在线观看 | 久久久久久久久精 | 色停停五月天 | 国产精品无av码在线观看 | 中文字幕在线播放视频 | 久久久久久国产精品免费 | 国产女做a爱免费视频 | 国产资源网 | av在线免费不卡 | 国产 视频 高清 免费 | 香蕉在线视频观看 | 在线视频你懂得 | 91久久久久久久一区二区 | 国产成人综 | 黄色aaaaa| 在线播放第一页 | 蜜臀av性久久久久蜜臀aⅴ四虎 | 国产精品手机视频 | 91视频一8mav | 黄污网站在线观看 | 97碰碰精品嫩模在线播放 | 狠狠天天 | 国产原创av在线 | 久久99精品久久久久婷婷 | 麻豆视频一区二区 | 精品极品在线 | 四虎国产精品免费 | 久久激情婷婷 | 97精产国品一二三产区在线 | 日韩av中文字幕在线 | 久久久久久久久久久综合 | 激情欧美一区二区三区免费看 | 亚洲成人一二三 | 国产精品久久久网站 | 国产99亚洲 | 久久精品日产第一区二区三区乱码 | 亚洲,播放 | 中文字幕在线播放日韩 | 免费国产在线精品 | 午夜精品99久久免费 | 久草在线综合 | 国产高清在线永久 | 99资源网| 黄色大全在线观看 | 日本黄色黄网站 | 亚洲精品视频在线观看免费视频 | 日韩电影中文 | 久久久99久久 | 国产日韩欧美自拍 | 欧美另类视频 | 日本视频久久久 | 亚洲va欧美va人人爽 | 中文字幕av免费在线观看 | 91久久奴性调教 | 日本精品午夜 | 精品国产一区二区三区久久久 | 日韩精品影视 | 偷拍精偷拍精品欧洲亚洲网站 | 免费看毛片网站 | 国产精品久久久久久久久久久免费 | 狠狠色丁婷婷日日 | 成人av电影免费观看 | 日韩精品免费一区二区三区 | 玖玖爱免费视频 | 亚洲视频高清 | 成人va天堂 | 欧美日本在线视频 | 久草色在线观看 | 久久久国产电影 | 久久国产电影 | 91丨九色丨蝌蚪丨老版 | 国产无套精品久久久久久 | 一区二区三区日韩在线 | 国产在线永久 | 欧美另类xxxxx | 国产精品美女久久久久久久 | 人人舔人人插 | 亚洲精品456在线播放第一页 | 乱男乱女www7788 | 欧美在线视频二区 | 久草在线一免费新视频 | 深夜男人影院 | 精品成人在线 | 久久久久免费精品国产 | 国产精品成人自产拍在线观看 | 欧美狠狠操| 欧美9999 | 亚洲高清视频在线 | 免费在线中文字幕 | 在线一区观看 | 婷婷综合电影 | 亚洲春色综合另类校园电影 | 在线视频 你懂得 | 中文在线免费一区三区 | 久久久在线观看 | 精品国产一区二区三区久久久蜜臀 | 久久久91精品国产一区二区三区 | 蜜臀久久99精品久久久久久网站 | 97超碰人人澡人人爱 | 91在线中文字幕 | 麻豆精品传媒视频 | 国产精品正在播放 | 国产97视频 | 丁香五香天综合情 | 久久黄色网址 | 人人看黄色 | 911久久香蕉国产线看观看 | 国产96在线| 久久国产视频网 | 国产精品久久久久久一二三四五 | 97免费视频在线播放 | 欧美日韩精品在线观看视频 | 日韩在线视频网站 | av免费看看 | v片在线播放 | 国产视频一区在线播放 | 国产精品永久在线 | 99免费| 久久新 | 久久男人中文字幕资源站 | 亚洲午夜在线视频 | 香蕉视频在线看 | 在线观看黄色小视频 | 日本中文字幕久久 | 天天操天天摸天天干 | 亚洲女在线 | 欧美激情视频一二三区 | 国产精品久久久网站 | 国产亚洲免费的视频看 | 国产黄色av影视 | 天天插天天干天天操 | 91九色最新地址 | 精品久久福利 | 中文字幕久久精品亚洲乱码 | 97狠狠干| 日韩视频在线一区 | 美女网站视频久久 | 国产视频在线观看一区二区 | 欧美日韩精品二区第二页 | 中国一级片在线观看 | 国产午夜麻豆影院在线观看 | 97成人资源站 | 亚洲一级黄色大片 | 99这里精品 | 国产精品一区二区久久精品爱微奶 | 久草视频在线免费看 | 免费在线观看av网站 | 在线观看av免费观看 | 国产中文字幕在线播放 | 免费成人黄色av | 成人97视频 | 国产高清视频在线播放一区 | 夜夜操狠狠操 | 久草在线综合 | 中文字幕在线免费看 | 亚洲国产欧美在线看片xxoo | av中文天堂 | 国产涩涩在线观看 | 午夜视频不卡 | 亚洲精品久久激情国产片 | 精品国产成人av在线免 | 丝袜网站在线观看 | 亚洲精品综合在线 | 18网站在线观看 | 狠狠色丁香久久婷婷综合五月 | 欧美有色 | 丁香5月婷婷 | 日韩在线高清 | av片子在线观看 | 麻豆传媒视频在线播放 | 欧美色噜噜 | 久久美女免费视频 | 久久国产剧场电影 | 日韩午夜网站 | 网站你懂的 | 99精品视频中文字幕 | 久久久久久久久久久影院 | 国产精品一区二区久久精品爱微奶 | 日韩午夜剧场 | 久久99精品国产麻豆婷婷 | 国产精品99久久久精品免费观看 | 国产网站av | 天天干天天干天天射 | 国产资源站 | 成人黄色片免费 | 成人免费观看大片 | 人人插人人玩 | 久久精品人人做人人综合老师 | 成人av免费在线观看 | 天天干天天上 | 成年人国产在线观看 | 天天操综合网站 | 成人av一区二区在线观看 | 亚洲六月丁香色婷婷综合久久 | 在线观看你懂的网站 | 蜜桃麻豆www久久囤产精品 | 久久激情五月激情 | 国产精品精品 | 99久免费精品视频在线观看 | 激情综合六月 | 在线a人v观看视频 | 99精品视频播放 | 一区久久久 | www.伊人色.com| 欧美二区在线播放 | 国产婷婷一区二区 | 欧美一二三四在线 | 永久中文字幕 | 91精品国产三级a在线观看 | 亚洲综合小说电影qvod | 99精品区 | 久久国产影视 | 国内丰满少妇猛烈精品播 | 日韩高清av | 2020天天干天天操 | 天天干天天玩天天操 | 久久久久久久久久久久久国产精品 | 国产一区二区不卡视频 | 日韩二区三区 | 午夜av一区二区三区 | 欧美一二三视频 | 国产婷婷一区二区 | 91精品一区在线观看 | 国产福利一区二区在线 | 96精品在线 | 亚洲精品色视频 | 久久综合色播五月 | 日韩久久精品一区二区三区下载 | 激情欧美在线观看 | 国产 字幕 制服 中文 在线 | 国产一级h| 亚洲视频免费在线 | 九九99| 免费观看国产视频 | 在线看国产视频 | 天天操天天干天天综合网 | 最新国产在线 | 国产黄大片在线观看 | 丁香婷婷激情国产高清秒播 | 日韩av视屏在线观看 | 亚洲激情网站免费观看 | 欧美大码xxxx | 欧美一级性生活视频 | 国产日韩欧美视频在线观看 | 亚洲美女视频在线观看 | 国产精品自产拍在线观看蜜 | japanesexxx乱女另类 | 国产69久久久| 一级成人在线 | 精精国产xxxx视频在线播放 | 伊人天堂av| 最近2019年日本中文免费字幕 | 日韩精品免费在线观看视频 | 色婷婷国产在线 | 99免费在线视频 | 日日摸日日碰 | 国产69精品久久99的直播节目 | 日日夜夜综合网 | 亚洲欧美日韩国产精品一区午夜 | 久久精彩免费视频 | 国产一级精品在线观看 | 国产乱码精品一区二区蜜臀 | 欧美精品一级视频 | 中文字幕一区在线观看视频 | 日韩超碰在线 | 丰满少妇在线观看网站 | 久久电影国产免费久久电影 | 在线中文字幕视频 | 精品国产自 | 亚洲一区二区三区四区在线视频 | 99自拍视频在线观看 | 911在线| 欧美色图东方 | 欧美成人黄色片 | 国产黄a三级三级三级三级三级 | 人人爽人人爽人人片av免 | 97碰碰精品嫩模在线播放 | 福利片视频区 | 成人av中文字幕在线观看 | 国产亚洲精品久久久久久移动网络 | 日韩在线不卡视频 | 中文字幕在线观看完整版 | 最近日本字幕mv免费观看在线 | 日韩av免费在线电影 | 国产成视频在线观看 | 国产又粗又猛又爽又黄的视频免费 | 九九激情视频 | 亚洲日日日 | 国产色黄网站 | 999久久久欧美日韩黑人 | 婷婷在线免费视频 | 久久伊人国产精品 | 久久艹在线 | 亚洲综合色网站 | 成人在线视频一区 | 亚洲永久精品在线 | 激情丁香婷婷 | 综合网五月天 | 97免费 | 国产精品成人在线 | 国产精品视频app | 九九精品视频在线看 | 97电影院在线观看 | 日日日天天天 | 久草免费在线视频观看 | 在线免费观看的av网站 | 亚洲精品乱码久久久久久高潮 | 在线视频精品播放 | 欧美成人亚洲成人 | 国内少妇自拍视频一区 | 在线观看亚洲精品视频 | 婷婷久久网 | 国产视频一二区 | 久久天天躁狠狠躁夜夜不卡公司 | 不卡国产在线 | 在线观看视频一区二区三区 | 99久久99久久精品国产片果冰 | 久久婷婷视频 | 国产一级在线看 | 欧美日韩三级在线观看 | 91麻豆免费视频 | 久久久久久久久久久免费 | av日韩精品 | 亚洲片在线资源 | 新版资源中文在线观看 | 91看片成人 | 在线观看av网站 | 中文字幕永久在线 | 深爱开心激情 | 亚洲欧美成人 | 欧美一区中文字幕 | 91av播放| 超碰免费公开 | 久久国产精品一二三区 | 中文字幕 国产 一区 | 五月开心婷婷 | 外国av网| 97人人看 | 国产精品亚洲a | 亚洲视频1 | 国产精品99在线播放 | 亚洲国产日韩一区 | 欧美精品久久久久久久久老牛影院 | 国产三级久久久 | 国产精品自在线 | 91精品视频导航 | 免费av福利 | 免费看v片 | 97香蕉超级碰碰久久免费软件 | 中文永久免费观看 | 夜夜干天天操 | 91av在线免费看 | 美女黄频视频大全 | 91成人欧美 | 香蕉视频在线免费看 | 手机av在线免费观看 | 中国一级片在线观看 | 夜夜夜夜爽 | 欧美久久久久久久久 | 亚洲人天堂 | 欧美日韩一级视频 | 五月天天在线 | 日韩精品2区 | 日韩av午夜 | 国产亚洲精品久久久久久 | 天天天天天天操 | 黄色aaa毛片 | 亚洲欧美国内爽妇网 | 亚洲欧洲精品一区 | 成人在线免费视频 | 国产精品露脸在线 | 超碰在线观看97 | 成人免费 在线播放 | 99精品在线播放 | 久久69av| 精品久久久久久亚洲综合网 | 在线色亚洲 | 欧美激情综合五月色丁香小说 | 天天综合精品 | 国产九九九视频 | h视频在线看| 日本丶国产丶欧美色综合 | 国产精品6999成人免费视频 | 四虎成人精品永久免费av | 亚洲国产中文字幕在线视频综合 | 91视频91色| 处女av在线| 日韩电影在线观看一区二区 | 日韩av免费大片 | 国产精品 国产精品 | 亚洲美女在线国产 | 国产精品久久久久9999吃药 | 午夜精品中文字幕 | 成人全视频免费观看在线看 | 在线观看中文字幕视频 | 开心色停停| 中文字幕av有码 | 国产呻吟在线 | 久久久久久久久福利 | 国产免费黄色 | www.91成人 | 久久精品人人做人人综合老师 | 免费美女av | 亚洲国产精品一区二区尤物区 | 免费在线日韩 | 国产精品久久久av久久久 | 久久污视频 | 亚洲午夜久久久久 | 国产在线国偷精品产拍免费yy | 天天干 天天摸 天天操 | 欧洲精品久久久久毛片完整版 | 久草在线资源免费 | 亚洲成人免费在线 | 日韩在线小视频 | 久久国产精品一区二区三区四区 | 久久极品| 一区三区视频 | 九月婷婷人人澡人人添人人爽 | 精品在线你懂的 | 欧美a级在线播放 | 婷婷激情综合网 | 欧美色图另类 | 92精品国产成人观看免费 | 98精品国产自产在线观看 | 99这里有精品| 国产资源免费在线观看 | 色视频国产直接看 | 欧美淫aaa免费观看 日韩激情免费视频 | 国产精品麻豆99久久久久久 | 免费a级毛片在线看 | 国产视频欧美视频 | 激情五月播播久久久精品 | 成人全视频免费观看在线看 | 日韩中文字幕免费在线观看 | 欧洲在线免费视频 | 免费视频成人 | 午夜av在线电影 | 久久久国产精品免费 | 国产高清不卡 | 色欲综合视频天天天 | 日日夜夜精品视频 | 韩国一区二区在线观看 | 在线日韩亚洲 | 五月色丁香 | 亚洲一区美女视频在线观看免费 | 国产无套精品久久久久久 | 一级片观看 | 奇人奇案qvod | 久久精品福利视频 | 黄a网站 | 精品一区精品二区高清 | 亚洲有 在线 | 视频一区在线免费观看 | 波多野结衣在线观看视频 | 在线观看黄色国产 | 日韩欧美在线免费观看 | 97超碰国产精品 | 欧美日韩网址 | 久草在线国产 | av在线一级 | 中文字幕高清免费日韩视频在线 | 精品久久久久久久久久久久久 | 国产91免费观看 | 狠狠色丁香九九婷婷综合五月 | 99精品免费网 | 狠狠色丁香婷婷综合欧美 | 国产又黄又爽又猛视频日本 | 久久不卡av | 日三级在线 | 精品久久国产一区 | 国产大尺度视频 | 日本在线视频一区二区三区 | 国产中文字幕第一页 | 久久久精品国产免费观看同学 | 97人人射 | 91人人澡人人爽人人精品 | 精精国产xxxx视频在线播放 | 久久久国产一区二区三区四区小说 | 中文字幕在线观看你懂的 | 五月情婷婷 | 久久黄色网址 | 97在线精品国自产拍中文 | 欧美日韩在线精品一区二区 | 激情五月网站 | 日韩成人xxxx | 久草视频免费播放 | 国产午夜三级一二三区 | 伊人色综合久久天天 | 99在线视频免费观看 | 国产精品三级视频 | 久久精品99国产精品亚洲最刺激 | 超碰com| 国产在线视频在线观看 | 国产精品情侣视频 | 视频在线观看国产 | 亚洲va男人天堂 | 四月婷婷在线观看 | www操操操 | 午夜av网站| 国产成人av网址 | 久久超碰免费 | 国产精品欧美日韩在线观看 | 色欧美视频 | www五月婷婷 | 中文国产字幕在线观看 | 色婷婷激情五月 | 人人爽人人爽人人爽学生一级 | 欧美日韩国产在线观看 | 久久电影国产免费久久电影 | 国产精品99久久久久久武松影视 | 黄网在线免费观看 | 国产精品中文字幕av | 国产黄色免费在线观看 | 精品91在线 | 九九久久久久99精品 | 日日麻批40分钟视频免费观看 | 中文字幕av最新 | 免费在线国产黄色 | 一区二区电影在线观看 | 久久久精品在线观看 | 久久只有精品 | 91色在线观看视频 | 欧美污污视频 | 在线 影视 一区 | 91精品国产欧美一区二区成人 | 日韩免费视频播放 | 中文字幕av全部资源www中文字幕在线观看 | 国产97在线播放 | 日韩电影在线观看中文字幕 | 欧美日韩亚洲第一页 | av动态图片 | 在线观看中文字幕dvd播放 | 一区二区视 | 亚洲综合精品视频 | 国产手机av在线 | 欧美大片在线观看一区 | 久久99久久99精品中文字幕 | 在线免费观看视频一区二区三区 | 亚洲精品国产精品乱码在线观看 | 亚洲一级电影 | 久久一区二区三区日韩 | 97超碰人| 久久精品国产v日韩v亚洲 | 国产小视频福利在线 | 国产在线精品一区二区不卡了 | 91精品国 | 中文字幕黄色网 | 国产午夜三级一区二区三桃花影视 | 在线色资源| 97在线免费视频观看 | 一区三区视频 | 国产精品18久久久久久不卡孕妇 | 国产精品一区二区视频 | 麻豆 videos | 成人精品在线 | 97超碰人人澡 | 不卡电影免费在线播放一区 | 久草在线免费看视频 | 久久av免费电影 | 成年人免费在线播放 | 亚洲激情 在线 | 日本中文不卡 | 久久久久久久久久久成人 | 视频直播国产精品 | 久久伊人精品一区二区三区 | 在线免费观看成人 | 欧美在线视频一区二区 | 午夜精品福利一区二区三区蜜桃 | 天天操天天射天天爱 | 人人爱在线视频 | 五月婷婷综合网 | 免费观看黄色av | 亚洲精品短视频 | 91社区国产高清 | 制服丝袜成人在线 | 久久九九视频 | 亚洲不卡av一区二区三区 | 中文字幕黄网 | 亚洲精品乱码久久久久久蜜桃欧美 | 天天色天天爱天天射综合 | 亚洲专区在线 | 亚洲人在线7777777精品 | 色婷婷成人网 | 久久久麻豆视频 | 国产 在线 高清 精品 | 久久你懂的| 成人av免费播放 | 亚洲 欧美变态 另类 综合 | 久久久免费av | 在线免费国产视频 | 午夜视频色 | 亚洲精品中文在线 | 一区二区三区日韩视频在线观看 | 久久亚洲在线 | 在线精品视频在线观看高清 | 久久99热精品 | 一区二区三区精品在线视频 |