MLEでのエラー処理

MLEのJavaScriptコードの実行中に発生したエラーは、データベース・エラーとして報告されます。

発生するデータベース・エラーは、発生したエラーのタイプによって異なります。たとえば、構文エラーではORA-04160が発生し、実行時エラー(捕捉されない例外など)ではORA-04161が発生します。各データベース・エラーのエラー・メッセージには、発生したエラーの簡単な説明が表示されます。また、DBMS_MLE PL/SQLパッケージには、動的MLE実行コンテキストまたは現在のセッションのMLEモジュールで発生した最後のエラーについて、MLE JavaScriptスタック・トレースを問い合せるプロシージャが用意されています。

DBMS_MLE.get_ctx_error_stack()をコールすると、DBMS_MLE.eval()をコールする場合と同じセキュリティ・チェックが行われます。したがって、他のユーザーが作成した動的MLE実行コンテキストで実行しているMLE JavaScriptコードのエラー・スタックは取得できません。

DBMS_MLEには、MLEモジュールの実行中に発生したアプリケーション・エラーについて、MLE JavaScriptスタック・トレースにアクセスする同様のファンクションDBMS_MLE.get_error_stack()が用意されています。このファンクションは、モジュール名と、オプションで環境名をパラメータとして使用し、コール仕様の指定された引数に基づいて、最新のアプリケーション・エラーのスタック・トレースを戻します。モジュール名または環境名が有効な識別子でない場合は、ORA-04170エラーが発生します。

MLEモジュールでは、コール元ユーザーに関連付けられたモジュール・コンテキストのエラー・スタックのみを取得できます。この制限により、エラー・スタックを介してユーザー間で機密情報が漏洩する可能性が回避されます。この制限の当然の結果として、他のユーザーが所有する定義者権限のMLEコール仕様を実行したときに発生したエラーのスタック・トレースは取得できません。

例9-7 ORA-04161エラーのスローとスタック・トレースの問合せ

次のコードを実行すると、ORA-04161エラーがスローされます:

CREATE OR REPLACE MLE MODULE catch_and_print_error_stack
LANGUAGE JAVASCRIPT AS

export function f(){
    g();
}

function g(){
    h();
}

function h(){
    throw Error("An error occurred in h()");
}
/

CREATE OR REPLACE PROCEDURE not_getting_entire_error_stack
AS MLE MODULE catch_and_print_error_stack
SIGNATURE 'f()';
/

BEGIN
    not_getting_entire_error_stack;
END;
/

結果:

BEGIN
*
ERROR at line 1:
ORA-04161: Error: An error occurred in h()
ORA-04171: at h (USER1.CATCHING_AND_PRINTING_ERROR_STACK:10:11)
ORA-06512: at "USER1.NOT_GETTING_THE_ENTIRE_ERROR_STACK", line 1
ORA-06512: at line 2
*/

プロシージャDBMS_MLE.get_error_stack()を使用して、このエラーについてスタック・トレースを問い合せることができます:

CREATE OR REPLACE PACKAGE get_entire_error_stack_pkg AS

    PROCEDURE get_entire_error_stack;
 
END get_entire_error_stack_pkg;
/

CREATE OR REPLACE PACKAGE BODY get_entire_error_stack_pkg AS

    PROCEDURE print_stack_trace( p_frames IN DBMS_MLE.error_frames_t ) AS
    BEGIN
        FOR i in 1 .. p_frames.count LOOP
            DBMS_OUTPUT.PUT_LINE( p_frames(i).func || '(' || 
            p_frames(i).source || ':' || p_frames(i).line || ')');
        END LOOP;
    END print_stack_trace;
 
    PROCEDURE do_the_work
    AS MLE MODULE catch_and_print_error_stack
    SIGNATURE 'f()';
 
    PROCEDURE get_entire_error_stack AS
        l_frames DBMS_MLE.error_frames_t;
    BEGIN
        do_the_work;
    EXCEPTION
    WHEN OTHERS THEN
        l_frames := DBMS_MLE.get_error_stack(
            'CATCH_AND_PRINT_ERROR_STACK'
        );
        print_stack_trace(l_frames);
        raise;
    END;
END get_entire_error_stack_pkg;
/

BEGIN
    get_entire_error_stack_pkg.get_entire_error_stack;
END;
/

前述のコードでは、元のエラーを発生させる前に、MLE JavaScript例外スタック・トレースを出力しています:

h(USER1.CATCH_AND_PRINT_ERROR_STACK:10)
g(USER1.CATCH_AND_PRINT_ERROR_STACK:6)
f(USER1.CATCH_AND_PRINT_ERROR_STACK:2)
BEGIN
*
ERROR at line 1:
ORA-04161: Error: An error occurred in h()
ORA-06512: at "USER1.GET_ENTIRE_ERROR_STACK_PKG", line 25
ORA-04171: at h (USER1.CATCH_AND_PRINT_ERROR_STACK:10:11)
ORA-06512: at "USER1.GET_ENTIRE_ERROR_STACK_PKG", line 11
ORA-06512: at "USER1.GET_ENTIRE_ERROR_STACK_PKG", line 18
ORA-06512: at line 2

コールアウトのエラー

MLE SQLドライバを介したSQLおよびPL/SQLへのコールアウト中に発生したデータベース・エラーは、自動的にJavaScript例外に変換されます。

ほとんどのデータベース・エラーについては、通常どおりJavaScriptコードでこれらの例外を捕捉して処理できます。ただし、重要なデータベース・エラーによる例外は捕捉できません。これには、次のものが含まれます:
  • 内部データベース・エラー(ORA-0600)

  • 致命的なデータベース・エラー(ORA-0603)

  • リソース制限を超えたためにトリガーされたエラー(ORA-04036)

  • ユーザー割込み(ORA-01013)

  • システム・エラー(ORA-7445)

捕捉されていないか再署名されたデータベース・エラーによって発生する例外により、MLEランタイム・エラー(ORA-04161)に加えて、元のデータベース・エラーが発生します。このような例外のJavaScriptスタック・トレースは、他のランタイム・エラーと同様にDBMS_MLE.get_error_stack()を使用して取得できます。

JavaScriptからのstdoutおよびstderrへのアクセス

MLEでは、JavaScriptコードから標準出力およびエラー・ストリームに書き込まれたデータにアクセスする機能が提供されます。

これらのストリームは、データベース・セッション内で、データベース・ユーザー、MLEモジュールおよび動的MLEコンテキストごとに個別に制御できます。いずれの場合も、ストリームは次のようにできます:
  • 無効化、

  • DBMS_OUTPUTにリダイレクト、または

  • ユーザーが指定したCLOBにリダイレクト

MLEモジュールのstdoutおよびstderrへのアクセス

DBMS_MLE PL/SQLパッケージには、各MLEモジュール・コンテキストの標準出力およびエラー・ストリームを制御するプロシージャset_stdout()およびset_stderr()が用意されています。

また、ファンクションDBMS_MLE.set_stdout_to_dbms_output()を使用して、stdoutDBMS_OUTPUTにリダイレクトできます。DBMS_MLEパッケージには、stderrをリダイレクトする類似のファンクションDBMS_MLE.set_stderr_to_dbms_output()が用意されています。

stdoutおよびstderrは、それぞれDBMS_MLE.disable_stdout()およびDBMS_MLE.disable_stderr()をコールすることで、いつでもモジュールに対して無効にできます。

デフォルトでは、stdoutおよびstderrDBMS_OUTPUTにリダイレクトされます。

指定されたMLEモジュールによってエクスポートされるMLEファンクションのCURRENT_USERは、ファンクションがコールされたときのCURRENT_USER、およびファンクションが実行者権限であるか定義者権限であるかによって変化する場合があります。データベース・ユーザー(user1など)によるDBMS_MLE.set_stdout()またはDBMS_MLE.set_stderr()のコールでは、MLEモジュール内のコードがuser1の権限で実行されたときにのみ、適切なストリームがリダイレクトされます。

つまり、通常、あるデータベース・ユーザーが他のユーザーの代理としてMLEモジュールのコードの実行に関するstdoutおよびstderrの動作を制御することはできません。

これらのプロシージャはすべてモジュール名と、オプションで環境名を第1引数および第2引数として使用します。これにより、出力をリダイレクトする必要がある実行コンテキストが識別されます。環境名を省略すると、ベース環境を使用しているコンテキストが対象になります。また、set_stdoutおよびset_stderrは、ユーザー指定のCLOBを最後の引数として使用し、これにより出力の書込み先が指定されます。

例9-8 MLEモジュールのstdoutのCLOBおよびDBMS_OUTPUTへのリダイレクト

次のJavaScriptモジュールについて考えてみます:

CREATE OR REPLACE MLE MODULE hello_mod 
LANGUAGE JAVASCRIPT AS 
    export function hello() { 
        console.log('Hello, World from MLE!'); 
    }
/

次のコール仕様により、エクスポートされたファンクションhello()をPL/SQLコードからのコールに使用できるようになります。

CREATE OR REPLACE PROCEDURE MLE_HELLO_PROC
AS MLE MODULE hello_mod SIGNATURE 'hello';
/

次のコードは、モジュールhello_modstdoutを、後で確認可能なCLOBにリダイレクトします:

SET SERVEROUTPUT ON; 
DECLARE 
    l_output_buffer CLOB; 
BEGIN
    -- create a temporary LOB to hold the output
    DBMS_LOB.CREATETEMPORARY(l_output_buffer, false);

    -- redirect stdout to a CLOB
    DBMS_MLE.SET_STDOUT('HELLO_MOD', l_output_buffer); 
    
    -- run the code
    MLE_HELLO_PROC(); 

    -- retrieve the output buffer
    DBMS_OUTPUT.PUT_LINE(l_output_buffer); 
END; 
/

この実行により、次の出力が生成されます:

Hello, World from MLE!

また、ファンクションDBMS_MLE.SET_STDOUT_TO_DBMS_OUTPUT()を使用して、stdoutDBMS_OUTPUTにリダイレクトできます:

SET SERVEROUTPUT ON;
BEGIN
    DBMS_MLE.SET_STDOUT_TO_DBMS_OUTPUT('HELLO_MOD');
    MLE_HELLO_PROC();
END;
/

これにより、以前と同じ出力が生成されます:

Hello, World from MLE!

動的MLEのstdoutおよびstderrへのアクセス

プロシージャDBMS_MLE.set_ctx_stdout()およびDBMS_MLE.set_ctx_stderr()を使用して、動的MLEコンテキストのstdoutおよびstderrをリダイレクトします。

同様に、DBMS_MLEパッケージにはプロシージャset_ctx_stdout_to_dbms_output()およびset_ctx_stderr_to_dbms_output()が用意されており、動的MLEコンテキストのstdoutおよびstderrDBMS_OUTPUTにリダイレクトします。

これらのファンクションのいずれかをコールすると、コンテキスト内で実行されているすべての動的MLEコードについて適切なストリームがリダイレクトされます。ただし、MLE SQLドライバを介したMLEファンクションのコールでは、そのファンクションを実装しているMLEモジュールのリダイレクト効果が使用されます。

例9-9 動的MLEstdoutCLOBおよびDBMS_OUTPUTへのリダイレクト

SET SERVEROUTPUT ON;
DECLARE
    l_ctx DBMS_MLE.context_handle_t;
    l_snippet CLOB;
    l_output_buffer CLOB;
BEGIN
    -- allocate the execution context and the output buffer
    l_ctx := DBMS_MLE.create_context();
    DBMS_LOB.CREATETEMPORARY(l_output_buffer, false);

    -- redirect stdout to a CLOB
    DBMS_MLE.SET_CTX_STDOUT(l_ctx, l_output_buffer);

    -- a bit of JavaScript code printing to the console
    l_snippet := 'console.log( "Hello, World from dynamic MLE!" )';
	
    -- execute the code snippet
    DBMS_MLE.eval(l_ctx, 'JAVASCRIPT', l_snippet);

    -- drop the execution context and print the output
    DBMS_MLE.drop_context(l_ctx);
    DBMS_OUTPUT.PUT_LINE(l_output_buffer);
END;
/

これによって、次の出力が生成されます。

Hello, World from dynamic MLE!