Oracle DatabaseにおけるJavaScript
このページでは、Oracle Database Multilingual Engine (MLE) を使用してOracle DatabaseでJavaScriptを実行する方法について説明します。MLEはGraalVMによって動作し、Linux x64上のOracle Database 23ai以降でJavaScriptコードを実行できます。
準備
- Oracle Cloud Free Tierアカウントを取得し、Oracle Database 23ai以降を提供しているホームリージョンを選択します。
- 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セキュリティを参照してください)。
-
最初の例では、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つの関数を定義しています。
string2obj()
obj2Str()
モジュールの末尾にある
export
キーワードは、これらの関数を他のMLEモジュール、PL/SQL、SQLから呼び出すことができることを意味します。 -
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" }
-
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 /
-
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
)の記述に集中できる優れたツールです。