Oracle DatabaseにおけるJavaScript

このページでは、Oracle Database Multilingual Engine (MLE) を使用してOracle DatabaseでJavaScriptを実行する方法について説明します。MLEはGraalVMによって動作し、Linux x64上のOracle Database 23ai以降でJavaScriptコードを実行できます。

準備

  1. Oracle Cloud Free Tierアカウントを取得し、Oracle Database 23ai以降を提供しているホームリージョンを選択します。
  2. Autonomous Database Transaction Processingインスタンスをプロビジョニングする と、お好みのSQL IDE(下記のいずれかなど)を起動します。

    前提条件Oracle Database 23c Free - Developer ReleaseにおけるJavaScript入門の「Oracle Database 23c Free - Developer Releaseで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はECMAScriptモジュールのみをサポートします。CommonJSやその他の一般的な手法は使用できません。

    上記のモジュールは、2つの関数を定義しています。

    1. string2obj()
    2. obj2Str()

    モジュールの末尾にあるexportキーワードは、これらの関数を他のMLEモジュール、PL/SQL、SQLから呼び出すことができることを意味します。

  2. 2番目の例では、これらの関数をSQLとPL/SQLで使用できるようにする方法を示します。JavaScriptモジュール内の関数は、SQLとPL/SQLで直接呼び出すことはできません。代わりに、それらに対して呼び出し仕様を作成します。呼び出し仕様は、PL/SQLの「ラッパー」と考えることができます。要件に応じて、スタンドアロンの関数とプロシージャを使用するか、パッケージにグループ化することができます。これらの2つの関数は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 Database 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 Database 23c Free - Developer ReleaseでJavaScriptコミュニティモジュールを使用するの説明に従って、ライセンスやコンプライアンスなどの注意事項に従い、人気のvalidator.jsモジュールをデータベースにロードします。これで、上記の資料に記載されているように、お気に入りのコンテンツ配信ネットワーク(CDN)からモジュールのECMAScript(ESM)バージョンをインポートできます。validatorモジュールがデータベースに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.3ではなく0.30000000000000004という値を出力します。精度の損失を回避するには、MLE JavaScript SQLドライバーからのPL/SQLとSQLの呼び出しに記載されている多くの戦略に従うことができます。1つのアプローチは、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 Database 21cは、DBMS_MLEパッケージの形でMLEを導入しました。これはまだ使用できますが、焦点はフレームワーク開発者とREPL(Read Eval Print Loop)システムへの埋め込みに移行しています。APEX、Database Actionsなどは、内部でこのパッケージを使用しています。

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;
/

DBMS_MLEはJavaScriptコードをスクリプトモードで評価するため、静的なimportステートメントを使用することはできません。ただし、上記のコードスニペットに示すように、動的なインポートを使用できます。

APEXとDatabase Actionsは、JavaScript(上記の例ではl_source_code)の記述に集中できる優れたツールです。

お問い合わせ