PL/SQL外部ファンクション・インタフェースの概要
外部ファンクション・インタフェース(FFI)は、よく知っているJavaScriptのような方法でPL/SQLパッケージに簡単にアクセスできるように設計されています。
mle-js-plsql-ffi
APIを使用すると、PL/SQLパッケージおよびプロシージャを中心にラッパーが作成されるため、後続のコールでは、JavaScriptオブジェクトおよびファンクションのようにラッパーと対話できます。この方法は、MLE JavaScript SQLドライバの代替として使用できる場合があります。
多くのデータベース機能は、PL/SQLパッケージ(組込み、APEXなどのフレームワークによってインストールされたもの、またはユーザー定義PL/SQLコード)の形式で使用できます。外部ファンクション・インタフェース(FFI)を使用すると、SQL文を実行せずに、パッケージおよびプロシージャのPL/SQL機能にJavaScriptコードから直接アクセスできるため、既存のPL/SQL機能をサーバー側のJavaScriptアプリケーションとシームレスに統合できます。たとえば、データベース・プロシージャをJavaScriptファンクションとして起動し、JavaScript値をファンクション引数として渡すことができます。
session.execute
を使用して匿名PL/SQLブロック内のDBMS_RANDOM
パッケージを使用する次のJavaScriptスニペットについて考えてみます:
CREATE OR REPLACE FUNCTION get_random_number(
p_lower_bound NUMBER,
p_upper_bound NUMBER
) RETURN NUMBER
AS MLE LANGUAGE JAVASCRIPT
{{
const result = session.execute(
'BEGIN :randomNum := DBMS_RANDOM.VALUE(:low, :high); END;',
{
randomNum: {
type: oracledb.NUMBER,
dir: oracledb.BIND_OUT
}, low: {
type: oracledb.NUMBER,
dir: oracledb.BIND_IN,
val: P_LOWER_BOUND
}, high: {
type: oracledb.NUMBER,
dir: oracledb.BIND_IN,
val: P_UPPER_BOUND
}
}
);
return result.outBinds.randomNum;
}};
/
SELECT get_random_number(1,100);
FFIを使用すると、前述の例の実装に必要なボイラープレート・コードを削減できます。次のスニペットは、前述のスニペットと同じ機能をより簡潔に実現します:
CREATE OR REPLACE FUNCTION get_random_number(
p_lower_bound NUMBER,
p_upper_bound NUMBER
) RETURN NUMBER
AS MLE LANGUAGE JAVASCRIPT
{{
const { resolvePackage } = await import ('mle-js-plsql-ffi');
const dbmsRandom = resolvePackage('dbms_random');
return dbmsRandom.value(P_LOWER_BOUND, P_UPPER_BOUND);
}};
/
SELECT get_random_number(1,100);
関連項目:
mle-js-plsql-ffi
APIの詳細は、サーバー側JavaScript APIのドキュメントを参照してください
- FFIを使用したオブジェクト解決
mle-js-plsql-ffi
APIでは一連のファンクションを使用でき、それぞれが対応するデータベースを表すJavaScriptオブジェクトを戻します。 - FFIを使用したサブプログラムへの引数の指定
arg
およびargOf
ファンクションを使用して、外部ファンクション・インタフェース(FFI)でIN OUT
およびOUT
パラメータを処理します。
FFIを使用したオブジェクト解決
mle-js-plsql-ffi
APIでは一連のファンクションを使用でき、それぞれが対応するデータベースを表すJavaScriptオブジェクトを戻します。
パッケージおよび最上位のファンクションおよびプロシージャの解決には、次のファンクションを使用できます:
resolvePackage('<pkg_name>')
resolveProcedure('<proc_name>')
resolveFunction('<func_name>')
解決するオブジェクトが自分のスキーマ内にあるか、パブリック・シノニムがある場合、オブジェクト名を所有スキーマで修飾することはオプションです。オブジェクトが別のスキーマにある場合は、オブジェクトにアクセスするために必要な権限があり、所有スキーマでその名前を修飾する必要があります。MLE JavaScript SQLドライバと同様に、すべての操作は独自のセキュリティ・コンテキストで実行されます。
ノート:
指定されたデータベース・オブジェクトが存在しないか、そのオブジェクトにアクセスできない場合、RangeError
が発生します。指定された名前が正しい型ではないデータベース・オブジェクトに解決されると、TypeError
が発生します。データベース・リンクはサポートされていません。データベース・リンクを使用して名前を解決しようとすると、Error
になります。
ノート:
提供されているFFIファンクションは、PL/SQLと同じ大/小文字の区別ルールに従います。つまり、名前はデフォルトで先頭文字が大文字になります。引用符で囲まれた識別子の場合、大/小文字の区別を示すために、二重引用符と一重引用符を組み合せたJavaScriptディクショナリ表記を使用する必要があります:
// call a procedure with case-sensitive name
myPkg['"MyProc"']();
// read a global variable with a case-sensitive name
console.log(myPkg['"MyVar"']);
データベース・オブジェクトが解決されると、結果オブジェクトに対して次の操作を実行できます:
- プロシージャ: 実行
- ファンクション: 実行
- パッケージ:
- プロシージャの実行
- ファンクションの実行
- パブリック・パッケージ変数の読取りおよび書込み
- 定数の読取り
resolvePackage
を使用すると、結果オブジェクトのプロパティ読取りを介して、変数、定数、プロシージャおよびファンクションに直接アクセスできます。パッケージにプロパティ読取りで指定されたメンバーがない場合は、Reference
エラーがスローされます。アクセスされたメンバーがPL/SQLファンクションまたはプロシージャの場合、JavaScriptオブジェクトは、最上位のファンクションおよびプロシージャに対して解決される同じタイプのコール可能エンティティを戻します。構文の例については、次のスニペットを考えてみます:
// resolve a package
const myPkg = resolvePackage('my_package');
// call a procedure and function in the package
myPkg.my_proc();
let result = myPkg.my_func();
// read a global variable and constant in the package
console.log(myPkg.my_var);
console.log(myPkg.my_const);
// write a global variable in the package
myPkg.my_var = 42;
パッケージ変数および定数の場合、名前付きでない型のみがサポートされます。PL/SQLレコード型、ネストした表型、連想配列、ベクトル型およびADTの型はサポートされていません。
プロシージャまたはファンクションを解決するときに、コール可能なオブジェクトを受け取ります。ファンクションでは、オプションでoverrideReturnType
インスタンス・メソッドを使用して戻り型を指定し、他のメタデータを変更できます。overrideReturnType
を使用してmaxSize
属性を増やす次の例を考えてみます:
- まず、文字列を戻すファンクションを作成します:
CREATE OR REPLACE FUNCTION ret_string( MULTIPLIER NUMBER ) RETURN VARCHAR2 AS BEGIN return rpad('this string might be too long for the defaults ', MULTIPLIER, 'x'); END; /
- FFIを使用してファンクション
ret_string
を解決する別のファンクションret_string_ffi
を作成します:CREATE OR REPLACE FUNCTION ret_string_ffi( MULTIPLIER NUMBER ) RETURN VARCHAR2 AS MLE LANGUAGE JAVASCRIPT {{ const retStrFunc = plsffi.resolveFunction('ret_string'); return retStrFunc(MULTIPLIER); }}; /
ret_string_ffi
ファンクションは、次のように、乗数値が十分に小さい値であれば動作します:SELECT ret_string_ffi(50);
結果:
RET_STRING_FFI(50) -------------------------------------------------------------------------------- this string might be too long for the defaults xxx
- 大きい乗数値の場合、結果はデフォルトのバッファ長200バイトを超える可能性があり、エラーが発生します:
SELECT ret_string_ffi(900);
結果:
SELECT ret_string_ffi(900) * ERROR at line 1: ORA-04161: Error: Exception during subprogram execution (6502): ORA-06502: PL/SQL: value or conversion error: character string buffer too small ORA-04171: at :=> (<inline-src-js>:3:12)
- この問題を解決するには、
overrideReturnType
インスタンス・メソッドを使用して、戻されたメッセージのmaxSize
属性を増やします:CREATE OR REPLACE FUNCTION ret_str_ffi_override( MULTIPLIER NUMBER ) RETURN VARCHAR2 AS MLE LANGUAGE JAVASCRIPT {{ const retStrFunc = plsffi.resolveFunction('ret_string'); // overrideReturnType accepts either an oracledb type constant // such as oracledb.NUMBER, or a string containing the name of a // user defined database type. If more information is needed, as // in this example, a parameter of type ReturnInfo can be provided retStrFunc.overrideReturnType({ maxSize: 1000 }); return retStrFunc(MULTIPLIER); }}; /
- 新しい
ret_str_ffi_override
ファンクションを使用すると、より大きな乗数のコールが機能するようになります:SELECT ret_str_ffi_override(900);
親トピック: PL/SQL外部ファンクション・インタフェースの概要
FFIを使用したサブプログラムへの引数の指定
arg
およびargOf
ファンクションを使用して、外部ファンクション・インタフェース(FFI)でIN OUT
およびOUT
パラメータを処理します。
JavaScriptとPL/SQLは、パラメータを異なる方法で処理します。たとえば、JavaScriptでは、PL/SQLと同じ方法で名前付きパラメータを使用できません。JavaScriptには、OUT
およびIN OUT
パラメータに相当するものはなく、ファンクションをオーバーロードするオプションもありません。最後に、JavaScript型はデータベースの組込み型システムと異なっています。JavaScriptからPL/SQLをコールできるようにするには、FFIがこれらの違いに対応している必要があります。
PL/SQLサブプログラム・パラメータの詳細は、『Oracle Database PL/SQL言語リファレンス』を参照してください。
- 複数のパラメータが定義されている。
- パラメータが、
IN
、OUT
およびIN OUT
モードの組合せを提供している。 VARCHAR2
OUT
変数のデフォルトのmaxSize
が不十分である
CREATE OR REPLACE PROCEDURE my_proc_w_args(
p_arg1 IN NUMBER,
p_arg2 IN NUMBER,
p_arg3 IN OUT JSON,
p_arg4 OUT TIMESTAMP,
p_arg5 OUT VARCHAR2
) AS
BEGIN
SELECT
JSON_TRANSFORM(p_arg3,
SET '$.lastUpdate' = systimestamp,
SET '$.value' = p_arg1 + p_arg2
)
into p_arg3;
p_arg4 := systimestamp;
-- the length of the string will exceed the default
-- length of 200 characters for the out bind, mandating
-- the use of maxSize in args().
p_arg5 := rpad('x', 255, 'x');
END;
/
IN
モードを使用して渡されるパラメータには、特別な処理は必要ありません。FFIには、OUT
およびIN OUT
パラメータをそれぞれ処理するためのarg()
およびargOf()
ファンクションが用意されています。FFIを使用して提供されるすべてのパラメータは基本的にバインド・パラメータであるため、session.execute()
を使用してPL/SQLを直接コールする場合に使用するdir
、val
、type
およびmaxSize
プロパティと同じものを使用して、それらの動作に影響を与えることができます。
arg
ファンクションは、引数を表すオブジェクトを生成します。オプションで、dir
、val
、type
およびmaxSize
プロパティの任意の組合せを含む、MLE JavaScript SQLドライバと同じオブジェクトを受け入れます。
argOf
ファンクションは、指定された値の引数を表すオブジェクトを生成します。
- 位置指定引数のリストとして。
- オブジェクトを使用して引数を指定し、名前付きパラメータをシミュレートする。
前述の例で作成したファンクションmy_proc_w_args
に基づいて、次のように位置指定引数を使用してFFIでファンクションを呼び出すことができます:
CREATE OR REPLACE PROCEDURE my_proc_w_args_positional(
"arg1" NUMBER,
"arg2" NUMBER
) AS MLE LANGUAGE JAVASCRIPT
{{
const myProc = plsffi.resolveProcedure('my_proc_w_args');
// arg3 is an IN OUT parameter of type JSON. my_proc_with_args
// will modify it in place and return it to the caller
const arg3 = plsffi.argOf({id: 10, value: 100});
// arg4 is a pure OUT parameter
const arg4 = plsffi.arg();
// arg5 represents an OUT parameter as well but due to the
// length of the return string, it must be provided with additional
// metadata
const arg5 = plsffi.arg({
maxSize: 1024
});
myProc(arg1, arg2, arg3, arg4, arg5);
console.log(`the updated JSON looks like this: ${JSON.stringify(arg3.val)}`);
console.log(`the calculation happened at ${arg4.val}`);
console.log(`the length of the string returned is ${arg5.val.length} characters`);
}};
/
2つ目のオプションは、単一のプレーンなJavaScriptオブジェクトとして提供される名前付き引数を使用することです。次に、FFI APIは各プロパティを、プロパティの名前に一致する引数にマップします。
CREATE OR REPLACE PROCEDURE my_proc_w_args_named(
"arg1" NUMBER,
"arg2" NUMBER
) AS MLE LANGUAGE JAVASCRIPT
{{
const myProc = plsffi.resolveProcedure('my_proc_w_args');
// arg3 is an IN OUT parameter of type JSON. my_proc_with_args
// will modify it in place and return it to the caller
const arg3 = plsffi.argOf({id: 10, value: 100});
// arg4 is a pure OUT parameter
const arg4 = plsffi.arg();
// arg5 represents an OUT parameter as well but due to the
// length of the return string must be provided with additional
// metadata
const arg5 = plsffi.arg({
maxSize: 1024
});
myProc({
p_arg1: arg1,
p_arg2: arg2,
p_arg3: arg3,
p_arg4: arg4,
p_arg5: arg5
});
console.log(`the updated JSON looks like this: ${JSON.stringify(arg3.val)}`);
console.log(`the calculation happened at ${arg4.val}`);
console.log(`the length of the string returned is ${arg5.val.length} characters`);
}};
/
JavaScriptでobject
として表される単一の引数を持つPL/SQLサブプログラムがあるエッジ・ケースに注意してください。直感的に、これを単一の位置指定引数として渡すことができますが、その場合、FFIはそれを名前付き引数オブジェクトとして解釈します。
- 名前付き引数でサブプログラムをコールしているように、オブジェクトに引数をラップできます。
- 引数は
plsffi.argOf()
でラップでき、FFIはそれを単一の位置指定引数として認識します。
これらのオプションを示す次の例を考えてみます:
-- PL/SQL subprogram we want to call
CREATE OR REPLACE PROCEDURE my_proc(my_arg JSON) AS
BEGIN
-- Process my_arg
END;
-- JavaScript function that calls my_proc
CREATE OR REPLACE PROCEDURE my_javascript_proc
AS MLE LANGUAGE JAVASCRIPT
{{
const myProc = plsffi.resolveProcedure('my_proc');
const myArg = { prop1: 10, prop2: 'foo' };
// Catch the exception that will happen if the FFI tries
// to interpret this as a call with named arguments
try {
myProc(myArg);
} catch (err) {
console.log(`if uncaught, this would have been a ${err}`);
}
// Option 1: Make it into a real named argument call.
myProc({ my_arg: myArg });
// Option 2: Wrap with argOf() to let the FFI know that it's a
// positional argument list call.
myProc(plsffi.argOf(myArg));
}};
PL/SQLを使用すると、開発者は、PL/SQLパッケージで定義されているファンクションおよびプロシージャのシグネチャをオーバーロードできます。FFIではオーバーロードの選択は実行されませんが、各引数のバインドに使用するPL/SQL型を決定する必要があります。残念ながら、すべての場合において、この決定を独自に行うことはできません。特に、次の場合に当てはまります:
- コールするために正しいシグネチャの判断に必要な引数にJavaScript値が指定されていない場合。値がないと、FFIには一致するPL/SQL型のセットを認識する方法がありません。
- 1つのJavaScript型が複数のPL/SQL型に対して実行可能な場合。
DB_TYPE_*
で始まる定数は、JavaScript値をPL/SQL値に変換する方法を制御します。- その他すべてのものは、戻されるPL/SQL値をJavaScript値に変換する方法を制御するために使用されます。
型解決に役立つように引数の型を指定する場合は、DB_TYPE_*
定数のいずれかを使用することをお薦めします。
次のPL/SQLパッケージを考えてみます。
CREATE OR REPLACE package overload_pkg AS
FUNCTION my_func(
p_arg1 IN BINARY_FLOAT
) RETURN VARCHAR2;
FUNCTION my_func(
p_arg1 IN INTEGER
) RETURN VARCHAR2;
END;
/
CREATE OR REPLACE PACKAGE BODY overload_pkg AS
FUNCTION my_func(
p_arg1 IN BINARY_FLOAT
) RETURN VARCHAR2 AS
BEGIN
RETURN 'binary_float';
END;
FUNCTION my_func(
p_arg1 IN INTEGER
) RETURN VARCHAR2 AS
BEGIN
RETURN 'integer';
END;
END;
/
このように、my_proc
はオーバーロードされ、BINARY_FLOAT
とINTEGER
の両方を受け入れます。JavaScriptでは、これらの両方の型が数値データ型として表されるため、複数のオーバーロードが有効です。FFI APIで正しい解決方法を選択できない場合は、PL/SQL型を指定して、特定のオーバーロードされたPL/SQLファンクションを強制的に実行できます。
CREATE OR REPLACE PROCEDURE force_overload
AS MLE LANGUAGE JAVASCRIPT
{{
const myPkg = plsffi.resolvePackage('overload_pkg');
let result = 'not yet called';
// Catch error ORA-04161: Error: Exception during subprogram execution
// (4161): Multiple subprograms match the provided signature
try {
result = myPkg.my_func(42);
} catch (err) {
console.log(`if uncaught, this would have been a ${err}`);
}
// Solution: use argOf to make this work
result = myPkg.my_func(plsffi.argOf(42, {type: oracledb.DB_TYPE_BINARY_FLOAT}))
console.log(`and the result is: ${result}`);
}};
/
型がユーザー定義の場合もエラーが発生します。たとえば、すべてのJavaScriptオブジェクトは、すべてのPL/SQLレコードに対して実行可能とみなされます。この場合、目的の型の名前を指定すれば十分です。
親トピック: PL/SQL外部ファンクション・インタフェースの概要