PLV8是PostgreSQL數(shù)據(jù)庫受信任的JavaScript語言擴(kuò)展。可以使用JavaScript來編寫PostgreSQL數(shù)據(jù)庫函數(shù)。本文介紹了如何安裝和使用PLV8插件。
功能優(yōu)勢
- 基于JavaScript,簡單易用。
- 使用Google的V8引擎,性能強(qiáng)大。
- 使SQL函數(shù)更加豐富。
安裝部署
- 安裝PLV8到數(shù)據(jù)庫。
CREATE EXTENSION plv8;
- 驗證安裝結(jié)果。
結(jié)果返回PLV8版本,表示安裝成功。SELECT plv8_version();
- 運行環(huán)境。
運行環(huán)境會話獨立,如果會話切換,則會初始化新的JS運行上下文,保證數(shù)據(jù)獨立。
- 初始化設(shè)置。
SET plv8.start_proc = 'xxx';
說明- 只有管理員可以進(jìn)行初始化設(shè)置。
- xxx表示初始化設(shè)置的函數(shù)或變量。示例如下:
//參考回歸文件startup.sql和startup_pre.sql set plv8.start_proc = startup; do $$ plv8.elog(NOTICE, 'foo = ' + foo) $$ language plv8;
使用指南
PLV8可以在PostgreSQL內(nèi)部執(zhí)行多種類型的函數(shù)調(diào)用,也可以使用多個綁定到PLV8對象的內(nèi)置函數(shù)。
- 標(biāo)量函數(shù)調(diào)用在PLV8中,您通常可以使用
CREATE FUNCTION
語句在JavaScript中編寫您需要調(diào)用的函數(shù)。示例如下:
在內(nèi)部,該函數(shù)定義如下:CREATE FUNCTION plv8_test(keys TEXT[], vals TEXT[]) RETURNS JSON AS $$ var o = {}; for(var i=0; i<keys.length; i++){ o[keys[i]] = vals[i]; } return o; $$ LANGUAGE plv8 IMMUTABLE STRICT; =# SELECT plv8_test(ARRAY['name', 'age'], ARRAY['Tom', '29']); plv8_test --------------------------- {"name":"Tom","age":"29"} (1 row)
(function(keys, vals) { var o = {}; for(var i=0; i<keys.length; i++){ o[keys[i]] = vals[i]; } return o; })
說明 其中。- keys和vals在PostgreSQL內(nèi)部進(jìn)行類型檢查和驗證,并作為函數(shù)的參數(shù)調(diào)用。
- o作為JSON類型返回給PostgreSQL的對象。如果在創(chuàng)建函數(shù)時省略了參數(shù)名稱,它們將在函數(shù)中以
$1
和$2
等形式出現(xiàn)。
- 集合返回函數(shù)調(diào)用PLV8支持從函數(shù)調(diào)用返回集合。
執(zhí)行結(jié)果如下:CREATE TYPE rec AS (i integer, t text); CREATE FUNCTION set_of_records() RETURNS SETOF rec AS $$ // plv8.return _next() 將記錄保存在內(nèi)部元組存儲中, // 并在函數(shù)終止時返回所有記錄。 plv8.return_next( { "i": 1, "t": "a" } ); plv8.return_next( { "i": 2, "t": "b" } ); // 您還可以采用JSON數(shù)組形式返回記錄。 return [ { "i": 3, "t": "c" }, { "i": 4, "t": "d" } ]; $$ LANGUAGE plv8;
SELECT * FROM set_of_records(); i | t ---+--- 1 | a 2 | b 3 | c 4 | d (4 rows)
說明- 如果函數(shù)聲明為
RETURNS SETOF
,每次調(diào)用函數(shù)時PLV8都會準(zhǔn)備一個元組存儲。您可以根據(jù)需要多次調(diào)用plv8.return_next()
函數(shù)來返回一行。此外,您還可以通過返回一個數(shù)組來添加一組記錄。 - 如果
return_next()
的參數(shù)對象具有參數(shù)未定義的額外屬性,則return_next()
會引發(fā)錯誤。
- 如果函數(shù)聲明為
- 觸發(fā)器函數(shù)調(diào)用PLV8支持觸發(fā)器函數(shù)調(diào)用:
如果觸發(fā)器類型是CREATE FUNCTION test_trigger() RETURNS TRIGGER AS $$ plv8.elog(NOTICE, "NEW = ", JSON.stringify(NEW)); plv8.elog(NOTICE, "OLD = ", JSON.stringify(OLD)); plv8.elog(NOTICE, "TG_OP = ", TG_OP); plv8.elog(NOTICE, "TG_ARGV = ", TG_ARGV); if (TG_OP == "UPDATE") { NEW.i = 102; return NEW; } $$ LANGUAGE "plv8"; CREATE TRIGGER test_trigger BEFORE INSERT OR UPDATE OR DELETE ON test_tbl FOR EACH ROW EXECUTE PROCEDURE test_trigger('foo', 'bar');
INSERT
或UPDATE
,您可以制定NEW變量的屬性來更改此操作存儲的實際元組。PLV8觸發(fā)器函數(shù)包含以下特殊變量:- NEW
- OLD
- TG_NAME
- TG_WHEN
- TG_LEVEL
- TG_OP
- TG_RELID
- TG_TABLE_NAME
- TG_TABLE_SCHEMA
- TG_ARGV
- 內(nèi)聯(lián)語句調(diào)用PostgreSQL 9.0及以上版本,PLV8支持
DO
塊。DO $$ plv8.elog(NOTICE, 'this', 'is', 'inline', 'code') $$ LANGUAGE plv8;
- JavaScript和數(shù)據(jù)庫內(nèi)置類型之間的自動映射對于結(jié)果和參數(shù),如果所需的數(shù)據(jù)庫列類型是以下之一,則會自動映射數(shù)據(jù)庫列類型和JavaScript數(shù)據(jù)類型。
- oid
- bool
- int2
- int4
- int8
- float4
- float8
- numeric
- date
- timestamp
- timestamptz
- bytea
- json (>= 9.2)
- jsonb (>= 9.4)
cstring
表示形式對其進(jìn)行轉(zhuǎn)換。 僅當(dāng)維度為1時才支持?jǐn)?shù)組類型。 JavaScript對象在使用時將映射到元組。 除這些類型外,PLV8還支持ANYELEMENT
和ANYARRAY
等復(fù)合類型。BYTEA
的轉(zhuǎn)換略有不同,詳情請參見類型化數(shù)組。 - 類型化數(shù)組PLV8提供了一種類型化數(shù)組,允許快速訪問本機(jī)內(nèi)存,主要是為了在瀏覽器中支持canvas。 PLV8通過它將
BYTEA
和各種數(shù)組類型映射到JavaScript數(shù)組。 對于BYTEA
,您可以將每個字節(jié)作為無符號字節(jié)數(shù)組訪問。 對于int2 /int4 /float4 /float8數(shù)組類型,PLV8通過使用PLV8域類型完成對每個元素的直接訪問。plv8_int2array
映射int2[]
plv8_int4array
映射int4[]
plv8_float4array
映射float4[]
plv8_float8array
映射float8[]
類型化數(shù)組僅僅是說明PLV8使用快速訪問方法而不是常規(guī)方法的注解。對于這些類型化數(shù)組,只有一維數(shù)組沒有任何NULL元素。目前沒有辦法在PLV8函數(shù)中創(chuàng)建這樣的類型化數(shù)組,只有參數(shù)可以是類型化數(shù)組。您可以修改元素并返回值。類型化數(shù)組的示例如下:CREATE FUNCTION int4sum(ary plv8_int4array) RETURNS int8 AS $$ var sum = 0; for (var i = 0; i < ary.length; i++) { sum += ary[i]; } return sum; $$ LANGUAGE plv8 IMMUTABLE STRICT; SELECT int4sum(ARRAY[1, 2, 3, 4, 5]); int4sum --------- 15 (1 row)
- 內(nèi)置函數(shù)PLV8提供了以下內(nèi)置函數(shù):
- plv8.elog:向客戶端或PostgreSQL日志文件發(fā)送消息。錯誤級別如下所示:
- DEBUG5
- DEBUG4
- DEBUG3
- DEBUG2
- DEBUG1
- LOG
- INFO
- NOTICE
- WARNING
- ERROR
var msg = 'world'; plv8.elog(DEBUG1, 'Hello', `${msg}!`);
- plv8.quote_literal、plv8.nullable、plv8.quote_ident:每個quote類的函數(shù)都與同名的內(nèi)置SQL函數(shù)相同。
- plv8.find_function:提供一個函數(shù)來訪問已在數(shù)據(jù)庫中注冊、定義PLV8函數(shù)的其他函數(shù)。
使用CREATE FUNCTION callee(a int) RETURNS int AS $$ return a * a $$ LANGUAGE plv8; CREATE FUNCTION caller(a int, t int) RETURNS int AS $$ var func = plv8.find_function("callee"); return func(a); $$ LANGUAGE plv8;
plv8.find_function()
,您可以查找其他PLV8函數(shù)。 如果查找結(jié)果不是PLV8函數(shù),則會引發(fā)錯誤。plv8.find_function()
的函數(shù)簽名參數(shù)是regproc
(僅函數(shù)名稱)或regprocedure
(具有參數(shù)類型的函數(shù)名稱)。 對于純JavaScript函數(shù),您可以將內(nèi)部類型作為參數(shù),使用void
類型作為返回類型,以確保不會發(fā)生來自SQL語句的任何調(diào)用。 - plv8.version:PLV8對象提供的版本字符串。該字符串對應(yīng)PLV8模塊版本。
- plv8.elog:向客戶端或PostgreSQL日志文件發(fā)送消息。錯誤級別如下所示:
- 通過SPI訪問數(shù)據(jù)庫
PLV8提供了用于數(shù)據(jù)庫訪問的函數(shù),包括預(yù)編譯語句和游標(biāo)。
- plv8.execute( sql [, args] ):執(zhí)行SQL語句并檢索結(jié)果。其中,
sql
參數(shù)必選,args
是一個可選數(shù)組包含sql
查詢中傳遞的任何參數(shù)。 對于SELECT
查詢,返回值是一個對象數(shù)組。 每個對象代表一行,對象屬性映射為列名。 對于非SELECT
命令,返回值是一個整數(shù),表示受影響的行數(shù)。var json_result = plv8.execute('SELECT * FROM tbl'); var num_affected = plv8.execute('DELETE FROM tbl WHERE price > $1', [ 1000 ]);
- plv8.prepare( sql [, typenames] ):打開或創(chuàng)建預(yù)編譯語句。
typename
參數(shù)是一個數(shù)組,其中每個元素都是一個字符串,對應(yīng)于每個綁定參數(shù)的數(shù)據(jù)庫類型名稱。 返回值是PreparedPlan
類型的對象。 在退出函數(shù)之前,該對象必須通過plan.free()
釋放。var plan = plv8.prepare( 'SELECT * FROM tbl WHERE col = $1', ['int'] ); var rows = plan.execute( [1] ); var sum = 0; for (var i = 0; i < rows.length; i++) { sum += rows[i].num; } plan.free(); return sum;
- PreparedPlan.execute( [args] ):執(zhí)行預(yù)編譯語句。其中,
args
參數(shù)和plv8.execute()
所需的參數(shù)相同。如果語句沒有任何參數(shù),則args
參數(shù)可以省略,其返回結(jié)果也和plv8.execute()
相同。 - PreparedPlan.cursor( [args] ):從預(yù)編譯語句中打開一個游標(biāo)。
args
參數(shù)和plv8.execute()
以及PreparedPlan.execute()
所需參數(shù)相同。返回值是游標(biāo)類型的對象。在退出函數(shù)之前,必須由Cursor.close()
關(guān)閉。var plan = plv8.prepare( 'SELECT * FROM tbl WHERE col = $1', ['int'] ); var cursor = plan.cursor( [1] ); var sum = 0, row; while (row = cursor.fetch()) { sum += row.num; } cursor.close(); plan.free(); return sum;
- PreparedPlan.free():釋放預(yù)編譯語句。
- Cursor.fetch( [nrows] ):如果省略了
nrows
參數(shù),則從游標(biāo)中獲取一行并將其作為對象返回(不是數(shù)組)。如果指定nrows
參數(shù),則獲取與nrows
參數(shù)相同的行數(shù),直至超過該行,并返回一個對象數(shù)組。 如果是負(fù)值,則向后獲取。 - Cursor.move( [nrows] ):將游標(biāo)移動到
nrows
指定的值。如果是負(fù)值則向后移動。 - Cursor.close():關(guān)閉游標(biāo)。
- plv8.subtransaction( func ):每次執(zhí)行時,
plv8.execute()
會創(chuàng)建一個子事務(wù)。 如果需要原子操作,則需要調(diào)用plv8.subtransaction()
來創(chuàng)建子事務(wù)塊。try{ plv8.subtransaction(function(){ plv8.execute("INSERT INTO tbl VALUES(1)"); // should be rolled back! plv8.execute("INSERT INTO tbl VALUES(1/0)"); // occurs an exception }); } catch(e) { ... do fall back plan ... }
說明 如果子事務(wù)塊中的一條SQL執(zhí)行失敗,則該事務(wù)塊中的所有操作都將回滾。 如果事務(wù)塊中的進(jìn)程引發(fā)JavaScript異常,則將繼續(xù)執(zhí)行。 因此,使用try ... catch
塊來捕獲異常,并在異常發(fā)生時執(zhí)行替代操作。
- plv8.execute( sql [, args] ):執(zhí)行SQL語句并檢索結(jié)果。其中,
- 窗口函數(shù)
您可以使用PLV8創(chuàng)建自定義的窗口函數(shù)。 PLV8通過封裝C-level窗口函數(shù)API以支持全部功能。 為創(chuàng)建窗口函數(shù),首先需要調(diào)用
plv8.get_window_object()
來創(chuàng)建窗口對象,plv8.get_window_object()
提供以下接口:說明 有關(guān)用戶自定義窗口函數(shù)的更多信息,請參見PostgreSQL官方文檔創(chuàng)建函數(shù)。- WindowObject.get_current_position():返回分區(qū)中的當(dāng)前位置,從0開始。
- WindowObject.get_partition_row_count():返回分區(qū)中的行數(shù)。
- WindowObject.set_mark_position( pos ):在指定行設(shè)置標(biāo)記。 此位置上方的行將消失,后續(xù)將無法訪問。
- WindowObject.rows_are_peers( pos1, pos2 ):如果pos1和pos2的行是對等的,則返回
true
。 - WindowObject.get_func_arg_in_partition( argno, relpos, seektype, mark_pos )、WindowObject.get_func_arg_in_frame(
argno, relpos, seektype, mark_pos ):
將
argno
中的參數(shù)值(從0開始)返回給該函數(shù),位置在距離當(dāng)前分區(qū)或幀中seektype
的relpos
行處。seektype
可以是WindowObject.SEEK_HEAD
,WindowObject.SEEK_CURRENT
或WindowObject.SEEK_TAIL
。 如果mark_pos
為true
,則標(biāo)記從中獲取參數(shù)的行。 如果指定的行不在分區(qū)/幀中,則返回的值為undefined
。 - WindowObject.get_func_arg_in_current( argno ):將
argno
中的參數(shù)值(從0開始)返回給該函數(shù)的當(dāng)前行處。說明 返回值與該函數(shù)的參數(shù)變量相同。 - WindowObject.get_partition_local( [size] ):返回分區(qū)本地值,該值在當(dāng)前分區(qū)終止時釋放。 如果沒有存儲任何內(nèi)容,則返回
undefined
。size
參數(shù)(默認(rèn)為1000)是第一次調(diào)用中分配的內(nèi)存大小。 一旦分配內(nèi)存,size
值就不會改變。 - WindowObject.set_partition_local( obj ):存儲分區(qū)本地值,您可以通過
get_partition_local()
檢索該值。 此函數(shù)在內(nèi)部使用JSON.stringify()
來序列化對象,因此,如果傳入一個無法序列化的值,則最終可能會是一個異常的值。 如果序列化值的大小超過分配的內(nèi)存,將引發(fā)異常。