JDBを使ったPL/SQLのデバッグ（実際の流れ）

# JDBを使ったPL/SQLのデバッグ（実際の流れ）

JDK付属のjdbを使ってPL/SQLのデバッグが行えることは前回書いたのですが、ちょっと不親切な記事だったので実際に動かした内容を書いていきます。


## 条件

- Oracle 11g
- ユーザはscottを使用
- インストール直後の環境で実行


## 初期設定

初期状態でscottとか使えなくなっているのでscottのロックを解除。あとデバッグができるように設定しています。

```sql
$ 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を足して画面に表示する、というストアドプロシージャを作ります。

```sql:ADD3.pls
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
SQL> set serveroutput on size 5000;
SQL> call add3(4);
result=7

コールが完了しました。

```

ちゃんと動いています。
デバッグをするにはデバッグ有効状態にする必要があるため、再コンパイルします。

```sql
SQL> alter procedure add3 compile debug;

プロシージャが変更されました。

SQL>
```

## デバッガ起動

```bash:jdb
$ jdb -connect com.sun.jdi.SocketListen:port=10000
次のアドレスでリスニング: ubuntu:10000
```

この方法で起動するとjdbはデバッギからの接続待ちになります。

続いてSQL*PLUS側からデバッガに接続してみます。引数はjdbで表示されたサーバ名とポート番号をそれぞれ指定します。（同一マシン上だからといってlocalhostを指定してもうまくいかないことがあります）


```sql:SQL*PLUS
SQL> begin dbms_Debug_jdwp.connect_tcp('ubuntu', '10000');
  2> end;
  3> /
```

デバッガに接続しました。SQL*PLUSはデバッガからの復帰待ちです。
jdb側を見てみます。


```shell-session: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]
```

ここで、ブレイクポイントを仕掛けて動かしてみます。


```shell-session:jdb
main[1] cont
> stop at $Oracle.Procedure.SCOTT.ADD3:1
遅延したブレークポイント$Oracle.Procedure.SCOTT.ADD3:1。
クラスがロードされた後に設定されます。
>
```

`cont`や`run`をするとSQL*PLUS側の処理が戻ります。


```sql:SQL*PLUS
SQL> begin dbms_Debug_jdwp.connect_tcp('ubuntu', '10000');
  2  end;
  3  /

PL/SQLプロシージャが正常に完了しました。

SQL>
```

おもむろにプロシージャを実行してみます。

```sql:SQL*PLUS
SQL> call add3(4);
```

SQL*PLUS側は止まり、デバッガに制御が移りました。

```shell-session:jdb
> 遅延したブレークポイント$Oracle.Procedure.SCOTT.ADD3:1の設定

ヒットしたブレークポイント: "スレッド=main", $Oracle.Procedure.SCOTT.ADD3.ADD3()、行=1 bci=1

main[1]
```

ソースを見てみます。

```shell-session:jdb
main[1] list
ソース・ファイルが見つかりません: ADD3.pls
```

そういえば、配置していませんでした。ソースは後で配置します。
続いて変数も見ていきます。

```shell-session:jdb
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`のフィールドを確認して、あたりをつけます。

```shell-session:jdb
main[1] fields $Oracle.Builtin.NUMBER
** フィールド・リスト **
java.lang.String _value
boolean _canBeNull
int _precision
int _scale
```

`_value`に値が入っていそうなので、`_value`の中身を確認してみます。

```shell-session:jdb
main[1] print NUM._value
 NUM._value = "4"
```

これです。
他の変数も見ていきましょう。

```shell-session:jdb
main[1] print NUM3._value
 NUM3._value = null
main[1] print RET._value
 RET._value = null
```

まだ、初期化されていないのでnullっています。
デバッグを進めてみます。

```shell-session:jdb
main[1] next
>
ステップが完了しました: "スレッド=main", $Oracle.Procedure.SCOTT.ADD3.ADD3()、行=3 bci=3
```

そういえばソースがないと激しく分かりにくいので、ソースを配置していきたいと思います。
以下のようにディレクトリを掘り、ソースを配置します。

```text:ディレクトリ構成
  /
  |- home/
  |  |- shingo/
  |  |  |- plsql/
  |  |  |  |- $Oracle/
  |  |  |     |- Procedure/
  |  |  |        |- SCOTT/
  |  |  |           |- ADD3.pls
```

そして、ソースの位置を教えてあげるとソースが表示されます。

```shell-session:jdb
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]
```

おー。すばらしい。

デバッグを進めて行きます。

```shell-session:jdb
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:SQL*PLUS
SQL> call add3(4);
result=7

コールが完了しました。

SQL>
```

変数も`set`を使えば書き換えられます。ここで`NUM3`の値を4に書き換えてみます。

```shell-session:jdb
ヒットしたブレークポイント:
ステップが完了しました: "スレッド=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:SQL*PLUS
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のデバッグが行えます）