PLV8是PostgreSQL資料庫受信任的JavaScript語言擴充。可以使用JavaScript來編寫PostgreSQL資料庫函數。本文介紹了如何安裝和使用PLV8外掛程式。
功能優勢
- 基於JavaScript,簡單易用。
- 使用Google的V8引擎,效能強大。
- 使SQL函數更加豐富。
安裝部署
- 安裝PLV8到資料庫。
CREATE EXTENSION plv8;
- 驗證安裝結果。
結果返回PLV8版本,表示安裝成功。SELECT plv8_version();
- 運行環境。
運行環境會話獨立,如果會話切換,則會初始化新的JS運行上下文,保證資料獨立。
- 初始化設定。
SET plv8.start_proc = 'xxx';
說明- 只有管理員可以進行初始化設定。
- xxx表示初始化設定的函數或變數。樣本如下:
//參考迴歸檔案startup.sql和startup_pre.sql set plv8.start_proc = startup; do $$ plv8.elog(NOTICE, 'foo = ' + foo) $$ language plv8;
使用指南
PLV8可以在PostgreSQL內部執行多種類型的函數調用,也可以使用多個綁定到PLV8對象的內建函數。
- 純量涵式調用在PLV8中,您通常可以使用
CREATE FUNCTION
語句在JavaScript中編寫您需要調用的函數。樣本如下:
在內部,該函數定義如下: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內部進行類型檢查和驗證,並作為函數的參數調用。
- o作為JSON類型返回給PostgreSQL的對象。如果在建立函數時省略了參數名稱,它們將在函數中以
$1
和$2
等形式出現。
- 集合返回函數調用PLV8支援從函數調用返回集合。
執行結果如下:CREATE TYPE rec AS (i integer, t text); CREATE FUNCTION set_of_records() RETURNS SETOF rec AS $$ // plv8.return _next() 將記錄儲存在內部元組儲存中, // 並在函數終止時返回所有記錄。 plv8.return_next( { "i": 1, "t": "a" } ); plv8.return_next( { "i": 2, "t": "b" } ); // 您還可以採用JSON數組形式返回記錄。 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)
說明- 如果函式宣告為
RETURNS SETOF
,每次調用函數時PLV8都會準備一個元組儲存。您可以根據需要多次調用plv8.return_next()
函數來返回一行。此外,您還可以通過返回一個數組來添加一組記錄。 - 如果
return_next()
的參數對象具有參數未定義的額外屬性,則return_next()
會引發錯誤。
- 如果函式宣告為
- 觸發器函數調用PLV8支援觸發器函數調用:
如果觸發器類型是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觸發器函數包含以下特殊變數:- NEW
- OLD
- TG_NAME
- TG_WHEN
- TG_LEVEL
- TG_OP
- TG_RELID
- TG_TABLE_NAME
- TG_TABLE_SCHEMA
- TG_ARGV
- 內聯語句調用PostgreSQL 9.0及以上版本,PLV8支援
DO
塊。DO $$ plv8.elog(NOTICE, 'this', 'is', 'inline', 'code') $$ LANGUAGE plv8;
- JavaScript和資料庫內建類型之間的自動對應對於結果和參數,如果所需的資料庫列類型是以下之一,則會自動對應資料庫列類型和JavaScript資料類型。
- oid
- bool
- int2
- int4
- int8
- float4
- float8
- numeric
- date
- timestamp
- timestamptz
- bytea
- json (>= 9.2)
- jsonb (>= 9.4)
cstring
表示形式對其進行轉換。 僅當維度為1時才支援數群組類型。 JavaScript對象在使用時將映射到元組。 除這些類型外,PLV8還支援ANYELEMENT
和ANYARRAY
等複合類型。BYTEA
的轉換略有不同,詳情請參見類型化數組。 - 類型化數組PLV8提供了一種類型化數組,允許快速存取本機記憶體,主要是為了在瀏覽器中支援canvas。 PLV8通過它將
BYTEA
和各種數群組類型映射到JavaScript數組。 對於BYTEA
,您可以將每個位元組作為無符號位元組數組訪問。 對於int2 /int4 /float4 /float8數群組類型,PLV8通過使用PLV8域類型完成對每個元素的直接存取。plv8_int2array
映射int2[]
plv8_int4array
映射int4[]
plv8_float4array
映射float4[]
plv8_float8array
映射float8[]
類型化數組僅僅是說明PLV8使用快速存取方法而不是常規方法的註解。對於這些類型化數組,只有一維數組沒有任何NULL元素。目前沒有辦法在PLV8函數中建立這樣的類型化數組,只有參數可以是類型化數組。您可以修改元素並傳回值。類型化數組的樣本如下: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)
- 內建函數PLV8提供了以下內建函數:
- plv8.elog:向用戶端或PostgreSQL記錄檔發送訊息。錯誤層級如下所示:
- 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類的函數都與同名的內建SQL函數相同。
- plv8.find_function:提供一個函數來訪問已在資料庫中註冊、定義PLV8函數的其他函數。
使用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函數。 如果尋找結果不是PLV8函數,則會引發錯誤。plv8.find_function()
的函數簽名參數是regproc
(僅函數名稱)或regprocedure
(具有參數類型的函數名稱)。 對於純JavaScript函數,您可以將內部類型作為參數,使用void
類型作為傳回型別,以確保不會發生來自SQL語句的任何調用。 - plv8.version:PLV8對象提供的版本字串。該字串對應PLV8模組版本。
- plv8.elog:向用戶端或PostgreSQL記錄檔發送訊息。錯誤層級如下所示:
- 通過SPI訪問資料庫
PLV8提供了用於資料庫訪問的函數,包括先行編譯語句和遊標。
- plv8.execute( sql [, args] ):執行SQL語句並檢索結果。其中,
sql
參數必選,args
是一個可選數組包含sql
查詢中傳遞的任何參數。 對於SELECT
查詢,傳回值是一個對象數組。 每個對象代表一行,對象屬性對應為列名。 對於非SELECT
命令,傳回值是一個整數,表示受影響的行數。var json_result = plv8.execute('SELECT * FROM tbl'); var num_affected = plv8.execute('DELETE FROM tbl WHERE price > $1', [ 1000 ]);
- plv8.prepare( sql [, typenames] ):開啟或建立先行編譯語句。
typename
參數是一個數組,其中每個元素都是一個字串,對應於每個綁定參數的資料庫類型名稱。 傳回值是PreparedPlan
類型的對象。 在退出函數之前,該對象必須通過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] ):執行先行編譯語句。其中,
args
參數和plv8.execute()
所需的參數相同。如果語句沒有任何參數,則args
參數可以省略,其返回結果也和plv8.execute()
相同。 - PreparedPlan.cursor( [args] ):從先行編譯語句中開啟一個遊標。
args
參數和plv8.execute()
以及PreparedPlan.execute()
所需參數相同。傳回值是遊標類型的對象。在退出函數之前,必須由Cursor.close()
關閉。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():釋放先行編譯語句。
- Cursor.fetch( [nrows] ):如果省略了
nrows
參數,則從遊標中擷取一行並將其作為對象返回(不是數組)。如果指定nrows
參數,則擷取與nrows
參數相同的行數,直至超過該行,並返回一個對象數組。 如果是負值,則向後擷取。 - Cursor.move( [nrows] ):將遊標移動到
nrows
指定的值。如果是負值則向後移動。 - Cursor.close():關閉遊標。
- plv8.subtransaction( func ):每次執行時,
plv8.execute()
會建立一個子事務。 如果需要原子操作,則需要調用plv8.subtransaction()
來建立子事務塊。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 ... }
說明 如果子事務塊中的一條SQL執行失敗,則該事務塊中的所有操作都將復原。 如果事務塊中的進程引發JavaScript異常,則將繼續執行。 因此,使用try ... catch
塊來捕獲異常,並在異常發生時執行替代操作。
- plv8.execute( sql [, args] ):執行SQL語句並檢索結果。其中,
- 視窗函數
您可以使用PLV8建立自訂的視窗函數。 PLV8通過封裝C-level視窗函數API以支援全部功能。 為建立視窗函數,首先需要調用
plv8.get_window_object()
來建立視窗對象,plv8.get_window_object()
提供以下介面:說明 有關使用者自訂視窗函數的更多資訊,請參見PostgreSQL官方文檔建立函數。- WindowObject.get_current_position():返回分區中的當前位置,從0開始。
- WindowObject.get_partition_row_count():返回分區中的行數。
- WindowObject.set_mark_position( pos ):在指定行設定標記。 此位置上方的行將消失,後續將無法訪問。
- 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
中的參數值(從0開始)返回給該函數,位置在距離當前分區或幀中seektype
的relpos
行處。seektype
可以是WindowObject.SEEK_HEAD
,WindowObject.SEEK_CURRENT
或WindowObject.SEEK_TAIL
。 如果mark_pos
為true
,則標記從中擷取參數的行。 如果指定的行不在分區/幀中,則返回的值為undefined
。 - WindowObject.get_func_arg_in_current( argno ):將
argno
中的參數值(從0開始)返回給該函數的當前行處。說明 傳回值與該函數的參數變數相同。 - WindowObject.get_partition_local( [size] ):返回分區本地值,該值在當前分區終止時釋放。 如果沒有儲存任何內容,則返回
undefined
。size
參數(預設為1000)是第一次調用中分配的記憶體大小。 一旦分配記憶體,size
值就不會改變。 - WindowObject.set_partition_local( obj ):儲存分區本地值,您可以通過
get_partition_local()
檢索該值。 此函數在內部使用JSON.stringify()
來序列化對象,因此,如果傳入一個無法序列化的值,則最終可能會是一個異常的值。 如果序列化值的大小超過分配的記憶體,將引發異常。