はじめに
Oracle PL/SQL開発者の多くは、次の2つの要望を持っています。
- コードを運用環境で実行したときにパフォーマンスを低下させることなく、できるだけ多くのデバッグコードを挿入したい。
- Oracleデータベースの最新バージョンの新しい機能を活用しつつ、以前のバージョンでも動作するコードを作成したい。ただし、データベースのバージョンごとに異なるコードをすべて詰め込んだ「ブロートウェア」(無意味に肥大化したソフトウェア)になることは避けたい。
他の言語の開発者たちは、以前からこれらの要望に応える手軽な解決手段を手に入れています。プリプロセッサとプリコンパイラです。例えば、C++のプリプロセッサでは条件付きコンパイルディレクティブと文字列置換を処理できるので、開発、運用、バージョンの異なるデータベースなど、目的に合わせてソースコードの別々のサブセットをコンパイルすることができます。残念ながらOracle PL/SQLにはこの機能がありませんでした。しかし、それも過去の話となりました。
最新リリースのOracle Database 10g R2には、これらの要望を簡単に満たすことができるPL/SQL条件付きコンパイル(Conditional Compilation)という新しい機能が備わっています。PL/SQL条件付きコンパイルは、厳密にはプリコンパイラやプリプロセッサではありません。Oracleが既存のPL/SQLコンパイル処理の初期ステージに追加した機能です。同じエンジンによって他のステージと一緒に実行されます。それでも、DBMS_PREPROCESSORパッケージ(これについては後で説明します)という名前に見られるように、PL/SQLはプリプロセッサと呼ばれることがあります。
本稿では、Oracle 10g R2の新しい条件付きコンパイル機能の使い方、DBMS_PREPROCESSORで条件付きコンパイルの結果を確認する方法、DBMS_DB_VERSIONでこの新しい機能を活用し、バージョンに依存しないコードを作成する方法、および条件付きコンパイルを使って軽量のデバッグフレームワークを作成する方法について説明します。
PL/SQL条件付きコンパイルの使い方
PL/SQL条件付きコンパイルの使い方はとても簡単です。必要なのは次の2種類の新しいセマンティック構造を覚えることだけです。
- コンパイラディレクティブ
- 条件付きコンパイルフラグ(ccflag)
条件付きコンパイルの最も基本的な原則は、コードを「条件付きで」コンパイルできる必要があることです。つまり、もし(IF)特定の条件が満たされた場合は特定のコードをコンパイルし、それ以外の場合は(ELSE)別のコードをコンパイルします。従って、条件付きコンパイルの最も基本的な構成要素は、if... then... else
構造のような働きをするコンパイラディレクティブということになります。
PL/SQL条件付きコンパイルでは、この構成要素を「選択ディレクティブ」と呼びます。選択ディレクティブは、PL/SQLのif... then... else
構造と非常によく似た働きをします。構文は次のとおりです。
$IF condition $THEN ..... [$ELSIF condition $THEN.... ] [$ELSE .....] $END
ご想像どおり、PL/SQL条件付きコンパイルのトークンの先頭には常にドル記号($
)が付きます。
条件付きコンパイルの選択ディレクティブと、標準のPL/SQLのif... then... else
構造には、次の2つの大きな違いがあります。
- 条件付きコンパイルの選択ディレクティブは、
$END $IF
ではなく$END
で終了します。 - 条件付きコンパイルのIFテストの条件式(condition)には、大きな制限があります。コンパイラはコンパイル時にconditionを評価する必要があるので、conditionには変数を使用できません。conditionはコンパイル時に定数値になっている必要があります。具体的には、静的なブール式である必要があります。この概念にとても近いのは、コンパイル時に静的になる式です(詳細については、コラム「静的なブール式」を参照してください)。
例えば、次の条件式は有効です。
$IF -4 = 8 $THEN ... $END
しかし、次のような条件式は指定できません。-4=TRUNC(sysdate,'MM')
は静的なブール式ではないからです。
$IF -4=TRUNC(sysdate,'MM') $THEN
...
$END
IFテスト式には、それ自体が静的なブール式であれば、パッケージ定数を含めることができます(もちろん、この定数は他のパッケージのどこかで設定しておく必要があります)。次に例を示します。
create or replace package PayrollApp_CC_Constants is c_SpecialNumber constant PLS_INTEGER := 8; c_DebugOn constant BOOLEAN := false; end PayrollApp_CC_Constants; .... $IF PayrollApp_CC_Constants.c_DebugOn $THEN debug('Debug On'); $ELSIF PayrollApp_CC_Constants.c_SpecialNumber = -4 $THEN raise; $END ....
debug('X')
は、PL/SQLの言語要素を示すものではありません。特に明記しない限り、これは例で使われている仮想のパッケージのどこか別の場所にある仮想のプロシージャを示しています。このプロシージャは、デバッグメッセージを引数に取り、それを出力するか、どこかに記録するという想定です。条件付きコンパイルのIFテスト式で静的な式の定数を使うときは、それらの定数を分離して、定数のみが含まれるパッケージにすべてまとめることをお勧めします。このようにすると、コンパイルの結果を左右する定数の値が一目で分かるようになります。また、PL/SQLコードの通常のアプリケーション定数と混同しにくくなるので、例えば定数が誤って静的でない値や式に設定されるのを防ぐことができます。さらに、定数を分離すると、コンパイルの循環依存関係に陥るおそれもなくなります。
次の例は、残りのコードがコンパイルどころか解析さえされないうちに、条件付きコンパイルの選択ディレクティブが処理されることを非常に明確に示しています。この簡単なプロシージャを見てください。
create or replace procedure testproc is begin $if TRUE $then dbms_output.put_line('We got here'); $else RANDOM BAD STUFF, NOT EVEN SYNTACTICALLY CORRECT $end null; end testproc;
このプロシージャのテキストには、大きな構文上の誤り("RANDOM BAD STUFF, NOT EVEN SYNTACTICALLY CORRECT
")が含まれていますが、この誤りは決して評価されることのない$IF - $THEN - $ELSE
の分岐内にあります。そのため、プロシージャは問題なくコンパイルされます。
SQL> create or replace procedure testproc is 2 begin 3 $if TRUE $then 4 dbms_output.put_line('We got here'); 5 $else 6 RANDOM BAD STUFF, NOT EVEN SYNTACTICALLY CORRECT 7 $end 8 null; 9 end testproc; 10 / Procedure created. SQL> show errors procedure testproc No errors.
次に示すのは、「静的な式」の例です。
18 'Pashmina' TRUE
c_myNumber constant pls_integer := 42;
c_myNumber*12
to_char(c_myNumber) || 'Pashmina'
c_myNumber = 43 -4 >= 8
c_myNumber CONSTANT PLS_INTEGER := trunc(sysdate,'MM') ;
c_myNumber*12 c_myNumber != 9