2022年2月4日金曜日

ORDSのPOSTハンドラ内の例外処理の確認

 ORDSのRESTハンドラ内で例外が発生した場合の動作を確認した。その作業のメモです。

表PKG_TESTを以下のDDLで作成する。

create table pkg_test(n number);


1行だけ初期データを投入する。

SQL> insert into pkg_test values(1);


1行挿入しました。


SQL> commit;


テストに使用するパッケージCHILD_PKGを作成するスクリプトを準備する。パッケージ変数のG_TESTは1である。ファイル名をchild_pkg1.sqlとして準備する。

create or replace package child_pkg

as

G_TEST NUMBER := 1;

function count_up(n number)

return number;

procedure count_in(n number);

end child_pkg;

/

 

create or replace package body child_pkg

as

function count_up(n number)

return number

is

begin

    G_TEST := G_TEST + n;

    return G_TEST;

end;

procedure count_in(n number)

is

begin

    G_TEST := G_TEST + n;

end;

end child_pkg;

/


パッケージCHILD_PKGを置き換えるスクリプトを準備する。パッケージ変数G_TESTは100である。また、追加のプロシージャを含む。このプロシージャは、パッケージを入れ替えたときに呼び出し元のパッケージのステータスを無効(INVALID)にするために追加しており、呼び出すことはない。

ファイル名はchild_pkg100.sqlとして準備する。

create or replace package child_pkg

as

G_TEST NUMBER := 100;

function count_up(n number)

return number;

procedure count_in(n number);

procedure count_in(n varchar2);

end child_pkg;

/

 

create or replace package body child_pkg

as

function count_up(n number)

return number

is

begin

    G_TEST := G_TEST + n;

    return G_TEST;

end;

procedure count_in(n number)

is

begin

    G_TEST := G_TEST + n;

end;

procedure count_in(n varchar2)

is

  l_v varchar2(100);

begin

  l_v := to_char(n); 

end;

end child_pkg;

/


パッケージCHILD_PKGを呼び出すプロシージャMY_PARENT_PKGを作成する。

パッケージの定義部は以下である。

create or replace package my_parent_pkg

as

function count_up(n number)

return number;

procedure count_in(n number);

end;

/


本体は以下である。

create or replace package body my_parent_pkg

as

function count_up(n number)

return number

is

begin

    return child_pkg.count_up(n);

end;

procedure count_in(n number)

is

begin

    child_pkg.count_in(n);

end;

end;

/


パッケージCHILD_PKGを入れ替えると、パッケージMY_PARENT_PKGのステータスはINVALIDになる。

以下のスクリプトを実行し、確認する。

set serveroutput on

select status from all_objects

where object_type = 'PACKAGE BODY'

  and object_name = 'MY_PARENT_PKG'

  and owner = 'APEXDEV';

begin

    dbms_output.put_line(my_parent_pkg.count_up(1));

end;

/

select status from all_objects

where object_type = 'PACKAGE BODY'

  and object_name = 'MY_PARENT_PKG'

  and owner = 'APEXDEV';


最初はG_TESTが1のCHILD_PKGがVALIDの状態で実行する。以下の結果が得られる。

SQL> @vca

SQL> set serveroutput on

SQL> select status from all_objects

  2  where object_type = 'PACKAGE BODY'

  3    and object_name = 'MY_PARENT_PKG'

  4    and owner = 'APEXDEV';


   STATUS 

_________ 

VALID     


SQL> begin

  2      dbms_output.put_line(my_parent_pkg.count_up(1));

  3  end;

  4  /

2



PL/SQLプロシージャが正常に完了しました。


SQL> select status from all_objects

  2  where object_type = 'PACKAGE BODY'

  3    and object_name = 'MY_PARENT_PKG'

  4    and owner = 'APEXDEV';


   STATUS 

_________ 

VALID     


SQL> 


G_TESTが100のCHILD_PKGに入れ替えて実行する。MY_PARENT_PKGを呼び出す前はパッケージCHILD_PKGのステータスはINVALIDだが、呼び出し後は自動コンパイルされているのでステータスはVALIDになる。例外は発生しない。

SQL> set echo off

SQL> @child_pkg100


Package CHILD_PKGがコンパイルされました



Package Body CHILD_PKGがコンパイルされました


SQL> set echo on

SQL> @vca

SQL> set serveroutput on

SQL> select status from all_objects

  2  where object_type = 'PACKAGE BODY'

  3    and object_name = 'MY_PARENT_PKG'

  4    and owner = 'APEXDEV';


    STATUS 

__________ 

INVALID    


SQL> begin

  2      dbms_output.put_line(my_parent_pkg.count_up(1));

  3  end;

  4  /

101



PL/SQLプロシージャが正常に完了しました。


SQL> select status from all_objects

  2  where object_type = 'PACKAGE BODY'

  3    and object_name = 'MY_PARENT_PKG'

  4    and owner = 'APEXDEV';


   STATUS 

_________ 

VALID     


SQL> 


ORDSのRESTサービスを定義し、POSTハンドラとして以下を記述する。

TEST - 0番


declare
l_n number;
l_stat varchar2(16);
begin
-- 呼び出し前のパッケージのステータスを確認する。
select status into l_stat from all_objects
where object_type = 'PACKAGE BODY'
and object_name = 'MY_PARENT_PKG'
and owner = 'APEXDEV';
sys.htp.p('SESSIONID=' || sys_context('USERENV','SESSIONID') || ',STATUS=' || l_stat);
-- ファンクションの呼び出し。
l_n := my_parent_pkg.count_up(0);
update pkg_test set n = l_n;
sys.htp.p(to_char(l_n));
-- 呼び出し後のパッケージのステータスを確認する。
select status into l_stat from all_objects
where object_type = 'PACKAGE BODY'
and object_name = 'MY_PARENT_PKG'
and owner = 'APEXDEV';
sys.htp.p('SESSIONID=' || sys_context('USERENV','SESSIONID') || ',STATUS=' || l_stat);
end;

CHILD_PKGのG_TESTが1の場合は、呼び出し結果は以下になる。SESSIONIDは実行時にコネクション・プールから取得されたコネクションに依存して随時変更される。ただし、同じコネクションが再度取得された(SESSIONIDが同じ値)場合でも、呼び出し結果は1で変わらない。処理の開始時にDBMS_SESSION.RESET_PACKAGEまたは同等のプロシージャが呼び出されている。

SESSIONID=2738504712,STATUS=VALID
1
SESSIONID=2738504712,STATUS=VALID

CHILD_PKGをG_TESTが100に入れ替えて実行する。初回実行は以下の結果になる。

SESSIONID=388841917,STATUS=INVALID
100
SESSIONID=388841917,STATUS=VALID

my_parent_pkg.count_up呼び出し前はパッケージMY_PARENT_PKGのステータスはINVAIDであるが、呼び出しす際に自動コンパイルが行われ、コードに間違いがなければVALIDになる。これ以降は、何回連続で実行しても結果は以下になる(SESSIONIDは変わる)。

SESSIONID=321954414,STATUS=VALID
100
SESSIONID=321954414,STATUS=VALID

TEST - 1番


POSTハンドラ内で2回続けて、MY_PARENT_PKG.COUNT_UPを呼び出す。

declare
l_n number;
l_stat varchar2(16);
begin
-- 呼び出し前のパッケージのステータスを確認する。
select status into l_stat from all_objects
where object_type = 'PACKAGE BODY'
and object_name = 'MY_PARENT_PKG'
and owner = 'APEXDEV';
sys.htp.p('SESSIONID=' || sys_context('USERENV','SESSIONID') || ',STATUS=' || l_stat);
-- ファンクションの呼び出し。
l_n := my_parent_pkg.count_up(10);
update pkg_test set n = l_n;
sys.htp.p(to_char(l_n));
-- もう一度ファンクションの呼び出し。
-- 引数が同じでもパッケージ変数が違うので値は変わる。
l_n := my_parent_pkg.count_up(10);
update pkg_test set n = l_n;
sys.htp.p(to_char(l_n));
-- 呼び出し後のパッケージのステータスを確認する。
select status into l_stat from all_objects
where object_type = 'PACKAGE BODY'
and object_name = 'MY_PARENT_PKG'
and owner = 'APEXDEV';
sys.htp.p('SESSIONID=' || sys_context('USERENV','SESSIONID') || ',STATUS=' || l_stat);
end;

CHILD_PKGのG_TESTが1の場合の結果は以下になる。

SESSIONID=4124054653,STATUS=VALID
11
21
SESSIONID=4124054653,STATUS=VALID

2回目の実行では前回実行の結果が追加される。ただし、RESTサービスの呼び出しを繰り返しても上記の結果は変わらない。

G_TESTが100のパッケージに入れ替えた後の初回の結果は以下になる。初回に限り、ステータスはINVALIDで始まる。

SESSIONID=388841917,STATUS=INVALID
110
120
SESSIONID=388841917,STATUS=VALID

TEST - 2番


1回目と2回目の処理の間に10秒待機する。この間にパッケージを入れ替える。

declare
l_n number;
l_stat varchar2(16);
begin
-- 呼び出し前のパッケージのステータスを確認する。
select status into l_stat from all_objects
where object_type = 'PACKAGE BODY'
and object_name = 'MY_PARENT_PKG'
and owner = 'APEXDEV';
sys.htp.p('SESSIONID=' || sys_context('USERENV','SESSIONID') || ',STATUS=' || l_stat);
-- ファンクションの呼び出し。
l_n := my_parent_pkg.count_up(20);
update pkg_test set n = l_n;
sys.htp.p(to_char(l_n));
-- 10秒、処理を待つ。
dbms_session.sleep(10);
-- もう一度ファンクションの呼び出し。
-- 引数が同じでもパッケージ変数が違うので値は変わる。
l_n := my_parent_pkg.count_up(20);
update pkg_test set n = l_n;
sys.htp.p(to_char(l_n));
-- 呼び出し後のパッケージのステータスを確認する。
select status into l_stat from all_objects
where object_type = 'PACKAGE BODY'
and object_name = 'MY_PARENT_PKG'
and owner = 'APEXDEV';
sys.htp.p('SESSIONID=' || sys_context('USERENV','SESSIONID') || ',STATUS=' || l_stat);
end;

以下のコードにてパッケージを参照していると、パッケージCHILD_PKGのコンパイルはPOSTハンドラの処理が終了するまで待機する。そのため、10秒待機の間にパッケージを置き換えることはできない。

l_n := my_parent_pkg.count_up(20);
update pkg_test set n = l_n;


TEST - 3番


PL/SQLではなく、SQLからパッケージを呼び出す形に変更する。

update pkg_test set n = my_parent_pkg.count_up(30);
select n into l_n from pkg_test fetch first 1 rows only;

全体としては以下になる。

declare
l_n number;
l_stat varchar2(16);
begin
-- 呼び出し前のパッケージのステータスを確認する。
select status into l_stat from all_objects
where object_type = 'PACKAGE BODY'
and object_name = 'MY_PARENT_PKG'
and owner = 'APEXDEV';
sys.htp.p('SESSIONID=' || sys_context('USERENV','SESSIONID') || ',STATUS=' || l_stat);
-- ファンクションの呼び出し。
update pkg_test set n = my_parent_pkg.count_up(30);
select n into l_n from pkg_test fetch first 1 rows only;
sys.htp.p(to_char(l_n));
-- 10秒、処理を待つ。
dbms_session.sleep(10);
-- もう一度ファンクションの呼び出し。
-- 引数が同じでもパッケージ変数が違うので値は変わる。
update pkg_test set n = my_parent_pkg.count_up(30);
select n into l_n from pkg_test fetch first 1 rows only;
sys.htp.p(to_char(l_n));
-- 呼び出し後のパッケージのステータスを確認する。
select status into l_stat from all_objects
where object_type = 'PACKAGE BODY'
and object_name = 'MY_PARENT_PKG'
and owner = 'APEXDEV';
sys.htp.p('SESSIONID=' || sys_context('USERENV','SESSIONID') || ',STATUS=' || l_stat);
end;

10秒待機中にパッケージを置き換えると、以下のエラーが発生する。

555 User Defined Resource Error

{
"code": "UserDefinedResourceError"
"title": "User Defined Resource Error"
"message": "The request could not be processed due to an error in a user defined resource"
"o:errorCode": "ORDS-25001"
"cause": "An error occurred when evaluating the SQL statement associated with this resource. SQL Error Code 4068, Error Message: ORA-04068: existing state of packages has been discarded ORA-04061: existing state of has been invalidated ORA-04061: existing state of package "APEXDEV.CHILD_PKG" has been invalidated ORA-04065: not executed, altered or dropped package "APEXDEV.CHILD_PKG" ORA-06508: PL/SQL: could not find program unit being called: "APEXDEV.CHILD_PKG" ORA-06512: at "APEXDEV.MY_PARENT_PKG", line 7 ORA-06512: at line 19 "
"action": "Ask the user defined resource author to check the SQL statement is correctly formed and executes without error"
"type": "tag:oracle.com,2020:error/UserDefinedResourceError"
"instance": "tag:oracle.com,2020:ecid/6b1d9ce3a59b9915441dc037877a5757"
}

2度目以降の実行は以下の結果になり、以降は繰り返し実行しても結果は変わらない。

SESSIONID=377084288,STATUS=VALID
130
160
SESSIONID=377084288,STATUS=VALID

TEST - 4番


2番目のファンクション呼び出しで発生する例外を無視する。

declare
l_n number;
l_stat varchar2(16);
begin
-- 呼び出し前のパッケージのステータスを確認する。
select status into l_stat from all_objects
where object_type = 'PACKAGE BODY'
and object_name = 'MY_PARENT_PKG'
and owner = 'APEXDEV';
sys.htp.p('SESSIONID=' || sys_context('USERENV','SESSIONID') || ',STATUS=' || l_stat);
-- ファンクションの呼び出し。
update pkg_test set n = my_parent_pkg.count_up(40);
select n into l_n from pkg_test fetch first 1 rows only;
sys.htp.p(to_char(l_n));
-- 10秒、処理を待つ。
dbms_session.sleep(10);
-- もう一度ファンクションの呼び出し。
-- 引数が同じでもパッケージ変数が違うので値は変わる。
begin
update pkg_test set n = my_parent_pkg.count_up(40);
select n into l_n from pkg_test fetch first 1 rows only;
exception
when others then
null;
end;
sys.htp.p(to_char(l_n)); -- 例外が発生したときは値が変わらない。
-- 呼び出し後のパッケージのステータスを確認する。
select status into l_stat from all_objects
where object_type = 'PACKAGE BODY'
and object_name = 'MY_PARENT_PKG'
and owner = 'APEXDEV';
sys.htp.p('SESSIONID=' || sys_context('USERENV','SESSIONID') || ',STATUS=' || l_stat);
end;

例外が発生しない場合の結果は、以下のように2回目の実行結果は前回の値が加算されている。

SESSIONID=1837758729,STATUS=VALID
41
81
SESSIONID=1837758729,STATUS=VALID

10秒待機中にパッケージを置き換えると、以下のように2回目の値は1回目と同じである。実際は2回目では例外が発生して実行されていない。

SESSIONID=675551166,STATUS=VALID
41
41
SESSIONID=675551166,STATUS=VALID

RESTサービスを続けて呼び出すと、以下の結果になる。パッケージが置き換わっているため、表示される数値は異なるが、2回目は加算されて表示される。これ以降の実行結果は同じである。

SESSIONID=1837758729,STATUS=VALID
140
180
SESSIONID=1837758729,STATUS=VALID


TEST - 5番


例外発生時にフラグを立て、それを見て再実行を行なう。

declare
l_n number;
l_stat varchar2(16);
l_exp boolean := false;
begin
-- 呼び出し前のパッケージのステータスを確認する。
select status into l_stat from all_objects
where object_type = 'PACKAGE BODY'
and object_name = 'MY_PARENT_PKG'
and owner = 'APEXDEV';
sys.htp.p('SESSIONID=' || sys_context('USERENV','SESSIONID') || ',STATUS=' || l_stat);
-- ファンクションの呼び出し。
update pkg_test set n = my_parent_pkg.count_up(50);
select n into l_n from pkg_test fetch first 1 rows only;
sys.htp.p(to_char(l_n));
-- 10秒、処理を待つ。
dbms_session.sleep(10);
-- もう一度ファンクションの呼び出し。
-- 引数が同じでもパッケージ変数が違うので値は変わる。
begin
update pkg_test set n = my_parent_pkg.count_up(50);
select n into l_n from pkg_test fetch first 1 rows only;
exception
when others then
l_exp := true;
null;
end;
sys.htp.p(to_char(l_n)); -- 例外が発生したときは値が変わらない。
-- もう一回トライする。
if l_exp then
begin
update pkg_test set n = my_parent_pkg.count_up(50);
select n into l_n from pkg_test fetch first 1 rows only;
exception
when others then
null;
end;
sys.htp.p(to_char(l_n)); -- 例外が発生したときは値が変わらない。
end if;
-- 呼び出し後のパッケージのステータスを確認する。
select status into l_stat from all_objects
where object_type = 'PACKAGE BODY'
and object_name = 'MY_PARENT_PKG'
and owner = 'APEXDEV';
sys.htp.p('SESSIONID=' || sys_context('USERENV','SESSIONID') || ',STATUS=' || l_stat);
end;

G_TESTが1のCHILD_PKGで実行した結果は以下になる。何回実行しても結果は同じである。

SESSIONID=1571721902,STATUS=VALID
51
101
SESSIONID=1571721902,STATUS=VALID

10秒待機の間にパッケージを入れ替える。結果は以下になる。数値が変わっていないので、二回目および3回目の実行でも例外が発生している。

SESSIONID=1837758729,STATUS=VALID
51
51
51
SESSIONID=1837758729,STATUS=VALID

RESTサービスとして再度呼び出すと以下の結果になる。パッケージが置き換わっているので印刷される数値は変わるが、正常終了している。これ以降は同じ結果が返される。

SESSIONID=1837758729,STATUS=VALID
150
200
SESSIONID=1837758729,STATUS=VALID

RESTサービスのハンドラ内で呼び出したプロシージャにて一旦例外が発生すると、それ以降は同じプロシージャの呼び出しは成功しない。RESTサービスの再呼び出しによって回復する。

テストに使用したRESTサービスのエクスポートを以下に置きました。
https://github.com/ujnak/apexapps/blob/master/exports/ORDS_REST_APEXDEV_oracle.error.4061_2022_02_07.sql

終了