OracleのPL/SQLで継承を使ってみた

OracleのPL/SQLで無理やりに継承を使ってみる。簡単な例でログ出力の仕組みでも作ってみた。

PL/SQLで継承を使うにはTYPEを作成してオブジェクト?を作成する。

--ログ出力用基本オブジェクト
CREATE OR REPLACE TYPE logger_typ AS OBJECT
 (
      MY_TYPE      varchar2(20),
      MEMBER PROCEDURE write( MSG IN logmsg_typ )
 ) NOT final;
/
--基本形 (DBMS_OUTPUT)
CREATE OR REPLACE TYPE BODY logger_typ AS
    MEMBER PROCEDURE write( MSG IN logmsg_typ ) IS
    BEGIN
       DBMS_OUTPUT.PUT_LINE(MSG.LOG_DATE   || ' ' ||
       MSG.LOG_LEVEL  || ' ' ||
       MSG.LOG_MSG    || ' ' ||
       MSG.LOG_HOST   || ' ' ||
       MSG.LOG_OSUSER || ' ' ||
       MSG.LOG_PRG    || ' ' ||
       MSG.LOG_SUPPLE );
    END;
END;
/

ログ出力用のオブジェクトなので、もっとも基本の形をDBMS_OUTPUTを使用したものにした。TYPEの中でwriteプロシジャを持たせている。

writeプロシジャへの引数としては出力するメッセージを格納したTYPEを渡している。

CREATE OR REPLACE TYPE logmsg_typ AS OBJECT
 (
 LOG_DATE     TIMESTAMP,
 LOG_LEVEL    varchar2(3),
 LOG_MSG      varchar2(1000),
 LOG_HOST     varchar2(30),
 LOG_OSUSER   varchar2(30),
 LOG_PRG      varchar2(50),
 LOG_SUPPLE   varchar2(2000)
 ) NOT final;
/

さて、DBMS_OUTPUT以外にテーブルへも格納したくなったので、基本形のオブジェクトを継承してwriteプロシジャをオーバーライドする。

-- logger_table_typ
-- テーブル出力型 (自律型トランザクションを使用)
CREATE OR REPLACE TYPE logger_table_typ UNDER logger_typ
 (
      OVERRIDING MEMBER PROCEDURE write( MSG IN logmsg_typ )
 ) NOT final;
/

CREATE OR REPLACE TYPE BODY logger_table_typ AS
     OVERRIDING MEMBER PROCEDURE write( MSG IN logmsg_typ ) IS
     PRAGMA AUTONOMOUS_TRANSACTION;
     BEGIN
        INSERT INTO APL_LOG_TABLE (
                   L_DATE,
                   L_LEVEL,
                   L_MSG,
                   L_HOST,
                   L_OSUSER,
                   L_PRG,
                   L_SUPPLE
        ) values (
                   MSG.LOG_DATE,
                   MSG.LOG_LEVEL,
                   MSG.LOG_MSG,
                   MSG.LOG_HOST,
                   MSG.LOG_OSUSER,
                   MSG.LOG_PRG,
                   MSG.LOG_SUPPLE
        );
       COMMIT;
 EXCEPTION
    WHEN OTHERS THEN
             DBMS_OUTPUT.PUT_LINE(SQLCODE);
             DBMS_OUTPUT.PUT_LINE(SQLERRM);
 END;
END;

使う側としては同じように呼び出しをするが、生成するオブジェクトをlogger_typにするかlogger_table_typにするかで出力先が画面出力かテーブル出力か切り替わる。

ちなみに、テーブル出力はログ記録を確実に残したいので自律型トランザクションを宣言して、呼び出し側のrollbackに関係なく記録されるようにするのは常識だ。

こんな感じで、ファイル出力型もあっていいかもしれない。これはこれで継承してwriteプロシジャの中身を変えてしまえばいい。

しかし、単純にこれだけでは使う方も面倒だ。例えば複数の記録媒体へログを記録したいときなどプログラムが複数のオブジェクトを直接操作することになる。そういうことを避けるためpackageを用意する。

CREATE OR REPLACE PACKAGE logger IS
/*===========================================================
 * plsqlログ出力用パッケージ
 *==========================================================*/
 --デフォルトのログ出力先
 DEFAULT_LOGGER varchar2(50) := 'DBMS_OUTPUT';

 --ログ出力オブジェクトの追加
 --引数なしで呼ばれる場合はDEFAULT_LOGGERがセットされる。
 PROCEDURE SET_LOGGER;
 PROCEDURE SET_LOGGER( logger_type IN varchar2 );
 --ログ出力オブジェクトの削除
 PROCEDURE DEL_LOGGER( logger_type IN varchar2 );

 --ログオブジェクトの数を取得
 FUNCTION GET_LOGGER_COUNT RETURN NUMBER;

 --ログ出力用
 PROCEDURE write( msg IN varchar2 );    
 PROCEDURE write( log_level IN varchar2,
 msg IN varchar2 );
 PROCEDURE write( err IN excep_typ );

END logger;
/

まずはシンプルに、このくらいの機能で始めてみる。(こえは仕様部)

SET_LOGGERは指定されたログ記録オブジェクトを用意するプロシジャ。これは複数のオブジェクトを保持できるように配列にして保持する。

--ログ出力オブジェクト格納エリア(複数のオブジェクトを格納可能とする)
 TYPE ArrayLoggerTyp IS TABLE OF logger_typ;
 ArrayLogger ArrayLoggerTyp;

ただし外部から直接操作させないために、packageヘッダー部ではなくbodyの方へ書く。

SET_LOGGERはこんな感じ。

----------------
 -- ログ出力オブジェクトの生成(指定あり)
 ----------------
 PROCEDURE SET_LOGGER( logger_type IN varchar2 ) IS
     my_logger logger_typ := NULL;
 BEGIN

 IF ArrayLogger.COUNT > 0 THEN
 --------------------
 -- 生成されるloggerが既に登録済みかチェック
 --------------------
     FOR i IN ArrayLogger.FIRST .. ArrayLogger.LAST
     LOOP
        IF ( ArrayLogger(i).MY_TYPE =  logger_type ) THEN
            RETURN;
        END IF;
     END LOOP;
 END IF;

 CASE upper(logger_type)
     WHEN 'DBMS_OUTPUT' THEN
        ArrayLogger.EXTEND;
        ArrayLogger(ArrayLogger.LAST) := logger_typ('DBMS_OUTPUT');
     WHEN 'TABLE' THEN
        ArrayLogger.EXTEND;
        ArrayLogger(ArrayLogger.LAST) := logger_table_typ('TABLE');
     WHEN 'FILE' THEN
        NULL;
     ELSE
        NULL;
 END CASE;

 EXCEPTION
     WHEN OTHERS THEN
        NULL;        
 END;

少し美しくない部分もあるが、まずはお試しということで良とする。オブジェクトのコンストラクタでは引数でどのタイプのログ出力オブジェクトかを表す文字列を渡している。これは同じタイプのオブジェクトを複数登録された場合にログ出力内容が重複してしまうのを防ぐためで、SET_LOGGERの最初に登録済みのオブジェクトと重複していないかを必ずチェックする。

DEL_LOGGERは指定されたログ記録オブジェクトを削除する。削除された時から対象のログ記録はされなくなる。

GET_LOGGER_COUNTはログ記録オブジェクトの数を知るため。(通常はあまり使用しないだろう)

writeプロシジャはいろいろな引数でログを記録するためのプロシジャ。単純に文字列を渡された場合や、ログレベルと文字列を渡された場合、例外が発生したときに例外オブジェクトを渡され記録する場合など。

ちなみに、例外オブジェクトというのも自前で作成する。これは例外処理用のpackageを別途用意しており、そこでオブジェクトが生成される。

テスト用のコードを書いてみる。

DECLARE
BEGIN
 -- デフォルトログ出力はDBMS_OUTPUTを使った画面出力
 logger.write('This is logger test!!');
 logger.write('E','This is logger ERROR test!!');
 DBMS_OUTPUT.PUT_LINE(logger.GET_LOGGER_COUNT);

 -- 重複したログ出力オブジェクトを指定しても問題ないか?
 logger.SET_LOGGER('DBMS_OUTPUT');
 logger.write('This is logger 2 test!!');
 logger.write('E','This is logger ERROR 2 test!!');
 DBMS_OUTPUT.PUT_LINE(logger.GET_LOGGER_COUNT);

 -- テーブル出力ログオブジェクトを追加
 -- この時ログ出力オブジェクトは2種類。DBMS_OUTPUTとTABLE
 -- 1回のwriteで両方に出力される。
 logger.SET_LOGGER('TABLE');
 logger.write('This is logger table test!!');
 logger.write('E','This is logger ERROR table test!!');
 DBMS_OUTPUT.PUT_LINE(logger.GET_LOGGER_COUNT);

 -- DBMS_OUTPUTログオブジェクトを削除
 -- この時点からログ出力はTABLEのみになる。
 logger.DEL_LOGGER('DBMS_OUTPUT');
 logger.write('This is logger table only test!!');
 logger.write('E','This is logger ERROR table only test!!');
 DBMS_OUTPUT.PUT_LINE(logger.GET_LOGGER_COUNT);


END;

お試しの感じでPL/SQLの継承機能を使ってみたが、やはり本格的なオブジェクト指向言語に比べものにはならない。そもそもそう言う場合はjavaを使えと言うことかもしれないが・・・

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

%d人のブロガーが「いいね」をつけました。