全部產品
Search
文件中心

PolarDB:PLV8

更新時間:Jul 06, 2024

PLV8是PostgreSQL資料庫受信任的JavaScript語言擴充。可以使用JavaScript來編寫PostgreSQL資料庫函數。本文介紹了如何安裝和使用PLV8外掛程式。

功能優勢

  • 基於JavaScript,簡單易用。
  • 使用Google的V8引擎,效能強大。
  • 使SQL函數更加豐富。

安裝部署

  • 安裝PLV8到資料庫。
    CREATE EXTENSION plv8;
  • 驗證安裝結果。
    SELECT plv8_version();
    結果返回PLV8版本,表示安裝成功。
  • 運行環境。

    運行環境會話獨立,如果會話切換,則會初始化新的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;
    })
    說明 其中。
    • keysvals在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');
    如果觸發器類型是INSERTUPDATE,您可以制定NEW變數的屬性來更改此操作儲存的實際元組。
    PLV8觸發器函數包含以下特殊變數:
    • NEW
    • OLD
    • TG_NAME
    • TG_WHEN
    • TG_LEVEL
    • TG_OP
    • TG_RELID
    • TG_TABLE_NAME
    • TG_TABLE_SCHEMA
    • TG_ARGV
    更多資訊,請參見PostgreSQL官方文檔觸發器函數
  • 內聯語句調用
    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)
    如果以上類型是JavaScript相容的,則可以轉換成功。 否則,PLV8會嘗試通過cstring表示形式對其進行轉換。 僅當維度為1時才支援數群組類型。 JavaScript對象在使用時將映射到元組。 除這些類型外,PLV8還支援ANYELEMENTANYARRAY等複合類型。 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模組版本。
  • 通過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建立自訂的視窗函數。 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開始)返回給該函數,位置在距離當前分區或幀中seektyperelpos行處。 seektype可以是WindowObject.SEEK_HEADWindowObject.SEEK_CURRENTWindowObject.SEEK_TAIL。 如果mark_postrue,則標記從中擷取參數的行。 如果指定的行不在分區/幀中,則返回的值為undefined

    • WindowObject.get_func_arg_in_current( argno ):將argno中的參數值(從0開始)返回給該函數的當前行處。
      說明 傳回值與該函數的參數變數相同。
    • WindowObject.get_partition_local( [size] ):返回分區本地值,該值在當前分區終止時釋放。 如果沒有儲存任何內容,則返回undefinedsize參數(預設為1000)是第一次調用中分配的記憶體大小。 一旦分配記憶體,size值就不會改變。
    • WindowObject.set_partition_local( obj ):儲存分區本地值,您可以通過get_partition_local()檢索該值。 此函數在內部使用JSON.stringify()來序列化對象,因此,如果傳入一個無法序列化的值,則最終可能會是一個異常的值。 如果序列化值的大小超過分配的記憶體,將引發異常。