在 Oracle 資料庫中使用 JavaScript

此頁面說明如何使用 Oracle 資料庫多語言引擎 (MLE) 在 Oracle 資料庫中執行 JavaScript。MLE 由 GraalVM 提供支援:它可以在 Linux x64 上的 Oracle 資料庫 23ai (及更高版本) 中執行 JavaScript 程式碼。

準備工作

  1. 取得 Oracle Cloud 免費層帳戶,並選擇提供 Oracle 資料庫 23ai 或更高版本的首選區域。
  2. 佈建 Autonomous Database Transaction Processing 執行個體 並啟動您最愛的 SQL IDE,例如以下其中一種:

    必要條件:請參閱在 Oracle 資料庫 23c 免費開發人員版本中介紹 JavaScript 中標題為「在 Oracle 資料庫 23c 免費開發人員版本中使用 JavaScript 的必要條件」的章節。(雖然目標是較早的版本,但它們仍然有效。)

範例

這些範例假設您已建立一個具有必要權限的資料庫帳戶,可以建立 MLE 模組和環境,以及 PL/SQL 函式、程序和套件。該帳戶也已獲授予執行 JavaScript 程式碼的權限。(如需更多資訊,請參閱MLE 安全性。)

  1. 第一個範例示範如何建立 MLE 模組。

     create or replace mle module helper_module_inline
     language javascript as
    
     /**
     * Convert a delimited string into key-value pairs and return JSON
     * @param {string} inputString - the input string to be converted
     * @returns {JSON}
     */
     function string2obj(inputString) {
         if ( inputString === undefined ) {
             throw `must provide a string in the form of key1=value1;...;keyN=valueN`;
         }
         let myObject = {};
         if ( inputString.length === 0 ) {
             return myObject;
         }
         const kvPairs = inputString.split(";");
         kvPairs.forEach( pair => {
             const tuple = pair.split("=");
             if ( tuple.length === 1 ) {
                 tuple[1] = false;
             } else if ( tuple.length != 2 ) {
                 throw "parse error: you need to use exactly one '=' between " +
                         "key and value and not use '=' in either key or value";
             }
             myObject[tuple[0]] = tuple[1];
         });
         return myObject;
     }
    
     /**
     * Convert a JavaScript object to a string
     * @param {object} inputObject - the object to transform to a string
     * @returns {string}
     */
     function obj2String(inputObject) {
         if ( typeof inputObject != 'object' ) {
             throw "inputObject isn't an object";
         }
         return JSON.stringify(inputObject);
     }
    
     export { string2obj, obj2String }
     /
    

    注意:MLE 僅支援 ECMA Script 模組。CommonJS 和其他常用技術無法使用。

    上述模組定義了兩個函式

    1. string2obj()
    2. obj2Str()

    模組結尾的 export 關鍵字表示其他 MLE 模組、PL/SQL 以及 SQL 可以呼叫這些函式。

  2. 第二個範例示範如何讓這些函式在 SQL 和 PL/SQL 中可用。JavaScript 模組中的函式無法在 SQL 和 PL/SQL 中直接呼叫。您需要為它們建立呼叫規格。呼叫規格可以被視為 PL/SQL 中的「包裝函式」。根據您的需求,您可以使用獨立的函式和程序,或將它們分組在套件中。由於這兩個函式屬於 helper_module,因此它們會放在一個套件中。

     create or replace package helper_pkg as
    
         function string2obj(
             p_inputString varchar2
         ) return JSON
             as mle module helper_module_inline
             signature 'string2obj';
    
         function obj2String(
             p_inputObject JSON
         ) return varchar2
             as mle module helper_module_inline
             signature 'obj2String';
    
     end helper_pkg;
     /
    

    在完成呼叫規格後,您可以將以 ; 分隔的字串轉換為 JavaScript 物件,並將其返回給呼叫方,如下所示

     select json_serialize(
         helper_pkg.string2obj(
             'order_id=1;order_date=2023-04-24T10:27:52;order_mode=mail;promotion_id=1'
         )
         pretty
     ) string2obj;
    

    您應該會看到以下輸出

     STRING2OBJ
     ----------------------------------------
     {
         "order_id" : "1",
         "order_date" : "2023-04-24T10:27:52",
         "order_mode" : "mail",
         "promotion_id" : "1"
             }
    
  3. 從 Oracle 資料庫 23.3 開始,如果您只需要呼叫標準 JavaScript 程式碼,則可以在呼叫規格中內嵌提供 JavaScript 程式碼,如下所示

     create or replace function date_to_epoch(
       "ts" timestamp
     ) return number
     as mle language javascript
     ;
     /
    

    內嵌 JavaScript 程序有一些限制,例如它們無法匯入其他 MLE 模組(但它們可以要求隨附 MLE 的程式庫)。這就是為什麼您應該考慮使用 MLE 模組來撰寫更複雜的邏輯和/或使其在其他模組中可重複使用。現在,您可以直接從 SQL 呼叫 date_to_epoch() 函式,如此範例所示

     select
       date_to_epoch (
         to_timestamp('2000-01-01 12:00:00', 'yyyy-mm-dd hh24:mi:ss')
       ) seconds_since_epoch
     /
    
  4. 您可以從其他模組呼叫 MLE 模組。通常在建立更複雜的邏輯或第三方模組時會發生這種情況。如在 Oracle 資料庫 23c 免費開發人員版本中使用 JavaScript 社群模組中所述,將常用的 validator.js 模組載入到資料庫中,並遵守授權和合規性等注意事項。現在,您可以如上文所述,從您最愛的內容傳遞網路 (CDN) 匯入模組的 ECMASCript (ESM) 版本。假設驗證器模組在資料庫中建立為 validator_module。您可以在自己的程式碼中使用該模組,如下所示

     create or replace mle module validator_usage_demo_module
     language javascript as
    
     import validator from 'validator';
    
     export function emailCheck(str) {
       return validator.isEMail(str);
     }
    

    將匯入名稱 (validator) 對應至模組的元素稱為MLE 環境。由於 MLE 無法使用檔案系統來解析匯入名稱,因此您必須明確宣告對應,如下所示

     create or replace mle env validator_env imports (
       'validator' module validator_module
     );
    

    MLE 環境有助於將呼叫規格中的匯入名稱對應至模組。如果您想公開 SQL 函式 emailCheck(),請建立下列呼叫規格

     create or replace function email_check(p_str varchar2)
     return boolean
     as mle module validator_usage_demo_module
     env validator_env
     signature 'emailCheck';
     /
    

    現在您幾乎可以在任何地方呼叫 email_check

類型轉換

了解類型轉換的工作方式非常重要 — 只要您的程式碼「離開」SQL 或 PL/SQL,類型就必須轉換為它們對應的 JavaScript 類型。(如需更多關於類型轉換的資訊,請參閱MLE 類型轉換。)

您可能知道 JavaScript Number 是以雙精確度 64 位元二進位格式 IEEE 754 值實作。它可能在儲存最大數字時發生問題,並導致精確度損失。以下範例示範了精確度可能損失的情況。

create table demo_table (
  id      number constraint pk_demo_table primary key,
  value   number not null
);

insert into demo_table(
  id,
  value
) values
  (1, 0.1),
  (2, 0.2);

create or replace procedure precision_loss(
  "id1" demo_table.id%type,
  "id2" demo_table.id%type)
as mle language javascript
;
/

begin precision_loss(1, 2); end;
/

precision_loss() 程序會在主控台中列印值 0.30000000000000004 (而不是 0.3)。為了避免精確度損失,您可以遵循從 MLE JavaScript SQL 驅動程式呼叫 PL/SQL 和 SQL中列出的許多策略。其中一種方法是指示 SQL 驅動程式將數字傳回為類型 OracleNumber:JavaScript 中 Oracle 的 Number 資料類型的包裝類型

create or replace procedure precision_preserved(
  "id1" demo_table.id%type,
  "id2" demo_table.id%type)
as mle language javascript
;
/

begin precision_preserved(1, 2); end;
/

precision_preserved() 程序現在會將正確的結果 (0.3) 列印到主控台。包裝資料類型記錄在 mle-js-plsqltypes 模組中。每當您需要數字精確度時,請考慮使用其中一種包裝資料類型。

動態 JavaScript 執行

Oracle 資料庫 21c 以 DBMS_MLE 套件的形式引入了 MLE。它仍然可用,但其重點已轉移到框架開發人員和嵌入到 REPL (讀取-求值-列印迴圈) 系統中。APEX、資料庫動作和其他工具會在幕後使用該套件。

以下是如何使用 DBMS_MLE 進行編碼的基本範例

set serveroutput on;
declare
    l_ctx           dbms_mle.context_handle_t;
    l_source_code   clob;
begin
    -- Create execution context for MLE execution
    l_ctx    := dbms_mle.create_context();

    -- using q-quotes to avoid problems with unwanted string termination
    l_source_code :=
q'~

    const result = session.execute(
        `select 'hello, world'`,
        [],
        {
            outFormat: oracledb.OUT_FORMAT_ARRAY
        }
    );

    const message = result.rows[0][0];

    console.log(message);

   ~';
    dbms_mle.eval(
        context_handle => l_ctx,
        language_id => 'JAVASCRIPT',
        source => l_source_code,
        source_name => 'example01'
    );

    dbms_mle.drop_context(l_ctx);
exception
    when others then
        dbms_mle.drop_context(l_ctx);
        raise;
end;
/

匿名 PL/SQL 區塊會在主控台中列印 hello, world。如果您想使用 MLE 內建模組以外的模組,則必須使用 MLE 環境。在先前建立的 validator_env 的基礎上,您可以使用 DBMS_MLE 來驗證電子郵件,如下所示

set serveroutput on;
declare
    l_ctx           dbms_mle.context_handle_t;
    l_source_code   clob;
begin
    -- Create execution context for MLE execution and provide an environment
    l_ctx    := dbms_mle.create_context('VALIDATOR_ENV');

    -- using q-quotes to avoid problems with unwanted string termination
    l_source_code :=
q'~
(async() => {
  const { default: validator } = await import ('validator');
  const str = 'not an email address';

  console.log(`Is ${str} a valid email address? ${validator.isEmail(str)}`);
})()
   ~';
    dbms_mle.eval(
        context_handle => l_ctx,
        language_id => 'JAVASCRIPT',
        source => l_source_code,
        source_name => 'example02'
    );

    dbms_mle.drop_context(l_ctx);
exception
    when others then
        dbms_mle.drop_context(l_ctx);
        raise;
end;
/

您無法使用靜態 import 陳述式,因為 DBMS_MLE 會以指令碼模式評估 JavaScript 程式碼。但是,您可以如上述程式碼片段所示使用動態匯入。

APEX 和資料庫動作是非常棒的工具,因為它們可讓您專注於撰寫 JavaScript (在上述範例中為 l_source_code)。

與我們聯繫