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
終了