JDBを使ったPL/SQLのデバッグ(実際の流れ)
JDK付属のjdbを使ってPL/SQLのデバッグが行えることは前回書いたのですが、ちょっと不親切な記事だったので実際に動かした内容を書いていきます。
条件
- Oracle 11g
- ユーザはscottを使用
- インストール直後の環境で実行
初期設定
初期状態でscottとか使えなくなっているのでscottのロックを解除。あとデバッグができるように設定しています。
$ sqlplus /nolog
SQL> conn / as sysdba;
SQL> alter user scott account unlock;
SQL> grant debug connect session to scott;
SQL> grant debug any procedure to scott;
SQL> conn scott/tiger;
SQL> パスワード設定
プロシージャの作成
引数で指定された値に3を足して画面に表示する、というストアドプロシージャを作ります。
create or replace procedure add3 (num in int)
as
num3 int := 3;
ret int;
begin
ret := num3 + num;
dbms_output.put_line('result='||ret);
end;
/
動作確認
SQL> set serveroutput on size 5000;
SQL> call add3(4);
result=7
コールが完了しました。
ちゃんと動いています。
デバッグをするにはデバッグ有効状態にする必要があるため、再コンパイルします。
SQL> alter procedure add3 compile debug;
プロシージャが変更されました。
SQL>
デバッガ起動
$ jdb -connect com.sun.jdi.SocketListen:port=10000
次のアドレスでリスニング: ubuntu:10000
この方法で起動するとjdbはデバッギからの接続待ちになります。
続いてSQL*PLUS側からデバッガに接続してみます。引数はjdbで表示されたサーバ名とポート番号をそれぞれ指定します。(同一マシン上だからといってlocalhostを指定してもうまくいかないことがあります)
SQL> begin dbms_Debug_jdwp.connect_tcp('ubuntu', '10000');
2> end;
3> /
デバッガに接続しました。SQL*PLUSはデバッガからの復帰待ちです。
jdb側を見てみます。
$ jdb -connect com.sun.jdi.SocketListen:port=10000
次のアドレスでリスニング: ubuntu:10000
捕捉されないjava.lang.Throwableの設定
遅延した捕捉されないjava.lang.Throwableの設定
jdbの初期化中...
>
VMが開始されました: "スレッド=main", $Oracle.PackageBody.SYS.DBMS_DEBUG_JDWP.<procedure$1>()、行=-1 bci=1
main[1]
ここで、ブレイクポイントを仕掛けて動かしてみます。
main[1] cont
> stop at $Oracle.Procedure.SCOTT.ADD3:1
遅延したブレークポイント$Oracle.Procedure.SCOTT.ADD3:1。
クラスがロードされた後に設定されます。
>
cont
やrun
をするとSQL*PLUS側の処理が戻ります。
SQL> begin dbms_Debug_jdwp.connect_tcp('ubuntu', '10000');
2 end;
3 /
PL/SQLプロシージャが正常に完了しました。
SQL>
おもむろにプロシージャを実行してみます。
SQL> call add3(4);
SQL*PLUS側は止まり、デバッガに制御が移りました。
> 遅延したブレークポイント$Oracle.Procedure.SCOTT.ADD3:1の設定
ヒットしたブレークポイント: "スレッド=main", $Oracle.Procedure.SCOTT.ADD3.ADD3()、行=1 bci=1
main[1]
ソースを見てみます。
main[1] list
ソース・ファイルが見つかりません: ADD3.pls
そういえば、配置していませんでした。ソースは後で配置します。
続いて変数も見ていきます。
main[1] locals
メソッド引数:
NUM = instance of $Oracle.Builtin.NUMBER(id=79)
ローカル変数:
NUM3 = instance of $Oracle.Builtin.NUMBER(id=80)
RET = instance of $Oracle.Builtin.NUMBER(id=81)
実際の値が見えません。。。
NUM
やNUM3
などのローカル変数は$Oracle.Builtin.NUMBER
のインスタンスということなので、$Oracle.Builtin.NUMBER
のフィールドを確認して、あたりをつけます。
main[1] fields $Oracle.Builtin.NUMBER
** フィールド・リスト **
java.lang.String _value
boolean _canBeNull
int _precision
int _scale
_value
に値が入っていそうなので、_value
の中身を確認してみます。
main[1] print NUM._value
NUM._value = "4"
これです。
他の変数も見ていきましょう。
main[1] print NUM3._value
NUM3._value = null
main[1] print RET._value
RET._value = null
まだ、初期化されていないのでnullっています。
デバッグを進めてみます。
main[1] next
>
ステップが完了しました: "スレッド=main", $Oracle.Procedure.SCOTT.ADD3.ADD3()、行=3 bci=3
そういえばソースがないと激しく分かりにくいので、ソースを配置していきたいと思います。
以下のようにディレクトリを掘り、ソースを配置します。
/
|- home/
| |- shingo/
| | |- plsql/
| | | |- $Oracle/
| | | |- Procedure/
| | | |- SCOTT/
| | | |- ADD3.pls
そして、ソースの位置を教えてあげるとソースが表示されます。
main[1] use /home/shingo/plsql
main[1] list
1 create or replace procedure add3 (num in int)
2 as
3 => num3 int := 3;
4 ret int;
5 begin
6 ret := num3 + num;
7 dbms_output.put_line('result='||ret);
8 end;
9 /
main[1]
おー。すばらしい。
デバッグを進めて行きます。
main[1] next
>
ステップが完了しました: "スレッド=main", $Oracle.Procedure.SCOTT.ADD3.ADD3()、行=5 bci=5
5 begin
main[1] list
1 create or replace procedure add3 (num in int)
2 as
3 num3 int := 3;
4 ret int;
5 => begin
6 ret := num3 + num;
7 dbms_output.put_line('result='||ret);
8 end;
9 /
main[1] print num3._value
com.sun.tools.example.debug.expr.ParseException: Name unknown: num3._value
num3._value = null
# 変数名は大文字じゃないとだめなようです。
main[1] print NUM3._value
NUM3._value = "3"
main[1] print RET._value
RET._value = null
main[1] next
>
ステップが完了しました: "スレッド=main", $Oracle.Procedure.SCOTT.ADD3.ADD3()、行=6 bci=6
6 ret := num3 + num;
main[1] list
2 as
3 num3 int := 3;
4 ret int;
5 begin
6 => ret := num3 + num;
7 dbms_output.put_line('result='||ret);
8 end;
9 /
main[1] next
>
ステップが完了しました: "スレッド=main", $Oracle.Procedure.SCOTT.ADD3.ADD3()、行=7 bci=7
7 dbms_output.put_line('result='||ret);
main[1] list
3 num3 int := 3;
4 ret int;
5 begin
6 ret := num3 + num;
7 => dbms_output.put_line('result='||ret);
8 end;
9 /
main[1] print RET._value
RET._value = "7"
main[1] next
>
ステップが完了しました: "スレッド=main", $Oracle.Procedure.SCOTT.ADD3.ADD3()、行=8 bci=8
8 end;
main[1] next
>
ここまで来ると制御がSQL*PLUSに戻ります。
SQL> call add3(4);
result=7
コールが完了しました。
SQL>
変数もset
を使えば書き換えられます。ここでNUM3
の値を4に書き換えてみます。
ヒットしたブレークポイント:
ステップが完了しました: "スレッド=main", $Oracle.Procedure.SCOTT.ADD3.ADD3()、行=1 bci=1
1 create or replace procedure add3 (num in int)
main[1] next
>
ステップが完了しました: "スレッド=main", $Oracle.Procedure.SCOTT.ADD3.ADD3()、行=3 bci=3
3 num3 int := 3;
main[1] next
>
ステップが完了しました: "スレッド=main", $Oracle.Procedure.SCOTT.ADD3.ADD3()、行=5 bci=5
5 begin
main[1] print NUM3._value
NUM3._value = "3"
main[1] set NUM3._value = "4"
NUM3._value = "4" = "4"
main[1] print NUM3._value
NUM3._value = "4"
main[1] cont
>
SQL> call add3(4);
result=8
コールが完了しました。
SQL>
NUM3
の値を4にしたので、4+4で8が表示されています。
まとめ
- デバッグを行うOracleユーザに対して権限を振るのを忘れない。
- デバッグするモジュールは
alter xxxx compile debug
でデバッグ可能にすること。 - ソースファイルの置き場所は
classes
で完全クラス名を見て確認できる。 - ソースファイル名は
list
で確認できる。 - 変数の参照は
print
で、変数の書き換えはset
でできる。 - (この方法を使うとEclipseでもRemote Java ApplicationからPLSQLのデバッグが行えます)