DBMS_SCHEDULERを使用したOSコマンドの実行と終了ステータスの関係

Oracle DatabaseからOSコマンドを実行するには、DBMS_SCHEDULERパッケージを使用する。

しかしこのDBMS_SCHEDULERパッケージを通してOSコマンドを実行した場合にシェルから実行した時と同じ終了ステータスを得る方法が(調べた限りでは)存在しない。
USER_SCHEDULER_JOB_RUN_DETAILS表の STATUS, ERROR#, ADDITIONAL_INFO 列が結果を得るために使えそうな列なのだが、以下の様な癖がある。

  • STATUS列: 終了ステータス0の場合にのみ「SUCCESS」となる。0以外だった場合は「FAILED」であり、一部の終了ステータスでは「STOPPED」となる。
  • ERROR#列: 基本的にはコマンドの終了ステータスが格納される。しかし例外がある(そもそもこの列の説明はマニュアル上「Error number in the case of an error」であり、コマンドの終了ステータスとはなっていないのだが、パッと動作や結果をみると同じに見えるので注意がいるのである)。
    • 終了ステータス 9,15,137,143 の場合にはERROR#は0になる。
    • コマンドが無い場合などに返す終了ステータスがシェルのそれと異なる。
  • ADDITIONAL_INFO列: 全く当てにならない。

結局のところDBMS_SCHEDULERパッケージを使用してOSコマンドを実行する場合には次のことを覚えておく必要がある。

  • USER_SCHEDULER_JOB_RUN_DETAILS表のERROR#はコマンドの終了ステータスと必ず一致するわけではないため、それだけをコマンドの結果として評価してはならない。特にkillされた場合はERROR#は0であり、完全に判定誤りとなる。
  • STATUS列が「SUCCESS」の場合には成功、「SUCCESS」ではない場合には失敗、の原始的な判定でよいならSTATUS列を使えばよい。
  • 終了ステータスを得たいのであれば、DBMS_SCHEDULER + αで自分で工夫しなければならない。(12cでは標準出力と標準エラー出力がUSER_SCHEDULER_JOB_RUN_DETAILS表に追加されているようなので工夫するにしても楽なのかもしれない)

環境

準備 /tmp/return.sh

#!/bin/bash

exit $1

DBMS_SCHEDULERによるOSコマンド実行

Oracle上でDBMS_SCHEDULERのCREATE_JOBプロシージャを使用して /tmp/return.sh を引数 0 から 257 まで与えて即時実行。

DECLARE
    JOBNAME VARCHAR2(30);
BEGIN
    FOR ret IN 0 .. 257 LOOP
        JOBNAME := DBMS_SCHEDULER.GENERATE_JOB_NAME('JOBTEST_RET_'||ret||'_');
        -- 外部プログラムの実行
        DBMS_SCHEDULER.CREATE_JOB(
            job_name => JOBNAME,
            job_type => 'EXECUTABLE',
            job_action => '/tmp/return.sh',
            number_of_arguments => 1,
            enabled => FALSE);
        DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE(JOBNAME,1,TO_CHAR(ret));
        DBMS_SCHEDULER.ENABLE(JOBNAME);
    END LOOP;
END;
/

USER_SCHEDULER_JOB_RUN_DETAILSによるOSコマンドの実行結果確認

ジョブ名(JOBTEST_RET_n_自動採番)と実行時刻をキーに参照する。実行結果を得られる列として ERROR# を使いたいので、return.sh に与えた引数がそのまま実行結果になって格納されることを確認する。

確認用SQL

下記SQLでは、与えた引数が0, 1, 2のそれぞれの場合と、与えた引数と ERROR# が一致しなかったケースを列挙する。

SELECT
    *
FROM
    (SELECT
         TO_NUMBER(REGEXP_REPLACE(REPLACE(JOB_NAME,'JOBTEST_RET_'),'_.*')) ARG1,
         STATUS,
         ERROR#,
         ADDITIONAL_INFO
     FROM
         USER_SCHEDULER_JOB_RUN_DETAILS 
     WHERE
         JOB_NAME LIKE 'JOBTEST_RET_%' AND LOG_DATE > SYSDATE-(5/(60*24)))
WHERE
    ERROR# <> ARG1 OR ARG1 IN (0,1,2)
ORDER BY
    ARG1;
確認結果

STATUSは0(と桁溢れで0と同等になる256)のみでSUCCEEDEDになり、0以外は原則FAILEDになってしまう。ただし、これらのコマンドも実行はされており、ERROR#はコマンドの終了ステータスと一致している。
注意を要するのが、9, 15, 137, 143 である。これらはSTATUSがSTOPPEDになっており、より特別な状態に見える。ただし、これらのコマンドも実行はされている。FAILEDと異なるのは、ERROR#が0になっていることだ。ERROR#列の値をそのままコマンドの終了ステータスと見なすことができないことがわかる。

ARG1  STATUS      ERROR#  ADDITIONAL_INFO
----  ----------  ------- ---------------------------------
0     SUCCEEDED   0       (null)
1     FAILED      1       "ORA-27369: タイプEXECUTABLEのジョブが、次の終了コードで失敗しました:
                           Operation not permitted"
2     FAILED      2       "ORA-27369: タイプEXECUTABLEのジョブが、次の終了コードで失敗しました:
                           No such file or directory"

9     STOPPED     0       REASON="Executable job was terminated"
15    STOPPED     0       REASON="Executable job was terminated"
137   STOPPED     0       REASON="Executable job was terminated"
143   STOPPED     0       REASON="Executable job was terminated"
256   SUCCEEDED   0       (null)
257   FAILED      1       "ORA-27369: タイプEXECUTABLEのジョブが、次の終了コードで失敗しました:
                           Operation not permitted"

9, 15, 137, 143 に共通するのは「強制停止」である。9はKILLシグナル、15はTERMシグナル、137, 143は128を引けばそれぞれ9, 15である。コマンドが他からkill -9された場合には終了ステータスは137、kill -15された場合には終了コードは143になる。しかし、この仕様には以下の不満がある。

  • killされた場合は137, 143であるので、なぜ9, 15がSTOPPEDなのかが不明。
  • STATUSをSTOPPEDにするのはよいが、ERROR#を0にしなくてもよいのではないか。

追加確認

存在しないコマンドを実行してみた場合のERROR#はどうなるのか。

DECLARE
    JOBNAME VARCHAR2(30);
BEGIN
    JOBNAME := DBMS_SCHEDULER.GENERATE_JOB_NAME('JOBTEST_NF_0_');
    -- 外部プログラムの実行
    DBMS_SCHEDULER.CREATE_JOB(
        job_name => JOBNAME,
        job_type => 'EXECUTABLE',
        job_action => '/tmp/notfound.sh',
        number_of_arguments => 1,
        enabled => FALSE);
    DBMS_SCHEDULER.SET_JOB_ARGUMENT_VALUE(JOBNAME,1,TO_CHAR(0));
    DBMS_SCHEDULER.ENABLE(JOBNAME);
END;
/
確認結果

ERROR#は2となる。ADDITIONAL_INFOのメッセージは妥当といえる。しかし、ファイルが存在しない場合の終了ステータスはシェルと異なる。

ARG1  STATUS      ERROR#  ADDITIONAL_INFO
----  ----------  ------- ---------------------------------
0     FAILED      2       "ORA-27369: タイプEXECUTABLEのジョブが、次の終了コードで失敗しました:
                           No such file or directory"
ファイルが存在しない場合のシェル上の終了ステータス

シェル上ではファイルが存在しない場合には終了コード127である。

# /tmp/returnx.sh
-bash: /tmp/returnx.sh: No such file or directory
# echo $?
127